In this tutorial, we’ll set up the foundation of our Node.js server using Express, a middleware library for Node.js that simplifies the creation of web applications and APIs.
You can follow along with the tutorial using our Client/Server template available on Github for download:
https://github.com/3dkrender/WAX_CS_Template
Assuming you want to start the project from scratch, let’s create a new Node.js application with the command `npm init -y` in the terminal and with the name you want your project to have.
Dependency Installation
We need to install some basic dependencies for our project:
- Express: Necessary for creating the Node.js server.
- Morgan: Middleware responsible for capturing events to log them. It’s not essential, but highly recommended to log and store as much information as possible about what happens on the server for later analysis.
- Winston: A flexible and versatile logging library for Node.js. It will work in conjunction with Morgan.
- Cors: Middleware for CORS management.
npm install express morgan cors winston
For TypeScript, it’s also necessary to install the type definition packages:
npm install @types/cors @types/express @types/morgan
Project Structure
We need to create the “routes” and “config” directories.
The project structure will look similar to this:
The ‘routes’ section will be covered in the next installment of the tutorial.
Morgan Configuration
Before starting with the server, let’s configure the logging system. In the ‘config’ folder, we create the ‘logger.ts’ file to configure how Winston will log the events collected from the Morgan middleware.
import { createLogger, format, transports } from 'winston';
const logger = createLogger({
level: 'info',
format: format.json(),
transports: [
new transports.File({ filename: 'logs/error.log', level: 'error' }),
new transports.File({ filename: 'logs/access.log' }),
new transports.Console({
format: format.combine(
format.colorize(),
format.simple(),
),
}),
],
});
export { logger };
When events start to arrive, Winston will create the ‘logs’ folder and the ‘error.log’ and ‘access.log’ files to log the events.
Server Installation
At the root of the project, we create the ‘server.js’ file and begin importing the dependencies.
import express from "express";
import { logger } from "./config/logger";
import morgan from "morgan";
import cors from "cors";
We create the server object and a constant to set the listening port:
const app = express();
const PORT = process.env.PORT || 3000;
We initialize the middlewares:
app.use(cors());
app.use(express.json());
app.use(morgan('combined'));
And start the server:
app.listen(PORT, async () => {
logger.info(`App listening to port ${PORT}`);
});
Launching the server:
From the system prompt, we execute the command:
ts-node server.ts
And we should receive the response from the server, ready and listening:
info: Server started on port 3000
If we notice, the ‘logs’ folder has been created within our project structure, and within it, the ‘access.log’ and ‘error.log’ files. Inside the ‘access.log’ file, we will find the same text that was displayed on the screen.
Recapitulation
At this point, the state of our project will be very similar to this:
Error Handling in API Requests
Let’s add two event handlers to the ‘server.ts’ file that we can find in any Express application, which will help us manage HTTP 404 (not found) and 500 (internal server error) errors.
Error Handler 404
This will be executed when Express doesn’t find any route on our server that matches the incoming request.
app.use((err: any, req: any, res: any, next: any) => {
const message = 'Route not found';
const statusCode = 404;
logger.warn(`${statusCode} - ${message} - ${req.originalUrl} - ${req.method} - ${req.ip}`);
res.status(statusCode).json({
status: "error",
statusCode,
message,
data: null,
});
next();
});
Error Handler 500
This method will be executed when an internal server error occurs during the processing of the requested information.
app.use((err: any, req: any, res: any, next: any) => {
const statusCode = err.statusCode || 500;
const message = err.message || "Internal Server Error";
logger.error(`${statusCode} - ${message} - ${req.originalUrl} - ${req.method} - ${req.ip}`);
res.status(statusCode).json({
status: "error",
statusCode,
message,
data: null,
});
next();
});
Note: Both methods end by calling the ‘next()’ function to pass control to the next middleware in the Express middleware chain.
Environment Variables Configuration
It’s common to use certain values as constants required to configure some services, such as the server’s port number or access data to external services. It’s a good practice to pass that information as system environment variables instead of including it explicitly in the program’s code. One of the many ways to do this is by using the ‘env-cmd’ library.
Installing the Node.js package
npm install env-cmd
Env-Cmd collects information from a JSON file to inject it as environment variables into the program when it’s running.
One of the advantages of the Env-Cmd library is that it allows us to create different environments in the same file, which will be very useful if we are going to test on the main WAX chain (mainnet) and the test chain (testnet), or if we simply want to have different configurations for the development and deployment versions of our application.
For our example, we’ll create a file named ‘.env-cmdrc.json’ in the root folder of the project and add the following content:
{
"TEST": {
"PORT": 3000
},
"MAIN": {
"PORT": 3005
}
}
With this file, we are configuring two working environments, ‘TEST’ and ‘MAIN’, which will serve us to set different values for the server’s listening port.
Now we need to modify the ‘packages.json’ file of our project to add the execution scripts:
"scripts": {
"dev": "env-cmd -r ./.env-cmdrc.json -e TEST ts-node-dev ./src/server.ts",
"start": "env-cmd -r ./.env-cmdrc.json -e MAIN ts-node ./src/server.ts",
},
Now, to run our server in a development environment, we execute the command:
npm run dev
And to run the project in production:
npm run start
Thank you for sticking with us this far! We hope this tutorial has been helpful for you. In the next installment, we’ll delve even further into server development. Don’t miss it!