Part Two: Enhancing our Express, Typescript and MongoDB project

Ermias Asmare
17 min readJun 9, 2024

--

In this part of the series Mastering Express, Typescript, and MongoDB: A Comprehensive Guide to Building and Deploying Web Applications. we are going to enhance the project setup we did on Part One of the series.

Topics

  • Middle ware Packages
  • Env validation
  • Error Handling
  • Security
  • Logging

Middleware Packages

  • body-parser parses the body of incoming HTTP requests and makes it available under req.body.
  • cookie-parser parses cookies attached to the client's request and populates req.cookies with an object keyed by the cookie names.
npm install body-parser cookie-parser

usage

app.use(bodyParser.urlencoded({ extended: true, limit: "30mb" }));
app.use(cookieParser());

Env validation — Environment variables play a crucial role in configuring your Express application. They often contain sensitive information such as API keys, database connection strings, and other configuration settings. Validating these environment variables is essential to ensure that your application runs smoothly and securely. Improper configuration can lead to application failures or security vulnerabilities.

For any kind of validation on our project, we are going to use a package called zod.

what is Zod — is a TypeScript-first schema declaration and validation library. With Zod, you can define a schema that maps all the different fields from the data to their respective types and requirements. Once you’ve created the schema, Zod ensures all user data adheres to the defined rules, helping you catch bugs and potential issues before they become critical.

npm install zod

now lets go to our validation folder inside src and create a file called env.validation.ts and paste the snippet below:

src/validaion/env.validation.ts

import { z } from 'zod';

export const envSchema = z.object({
PORT: z.string({ required_error: "Port number is required" }),
NODE_ENV: z.enum(['development', "production", "test"]),
MONGO_DB_URI: z.string({ required_error: "Db url is required" }),
});
export type EnvConfig = z.infer<typeof envSchema>;

Here’s a brief explanation of how to use `zod` for environment variable validation:

1. Import Zod: Start by importing `zod` from the library.

2. Define the Validation Schema**: Create a schema to specify the required environment variables and their types.

  • PORT: Ensures that the `PORT` environment variable is a string and is required.
  • NODE_ENV: Restricts the `NODE_ENV` environment variable to one of three specific values: ‘development’, ‘production’, or ‘test’.
  • MONGO_DB_URI: Ensures that the `MONGO_DB_URI` environment variable is a string and is required.

3. Infer TypeScript Type: Infer the TypeScript type from the schema for type-safe access to environment variables.

- EnvConfig: This type can be used throughout your application to ensure type safety when accessing environment variables.

By using `zod` to define and validate your environment variables, you can catch configuration errors early, ensure consistency, and maintain security. This approach provides a robust way to manage your application’s environment settings.

now lets go to our config folder in src and create a file called env.config.ts, on this file we will design a to validate and parse environment variables using the `dotenv` and `zod` libraries.

lets Import and load, dotenv, zod and the env schema we just created.

import dotenv from 'dotenv';
import { EnvConfig, envSchema } from '../validation/env.validation';
import { ZodError } from 'zod';

dotenv.config();
  • dotenv: A library to load environment variables from a `.env` file into `process.env`.
  • EnvConfig` and `envSchema`: Imports from a custom module (likely containing schema definitions for environment variables).
  • ZodError`: An error type from the `zod` library used for validation errors.

then lets write the validation function as such:

import dotenv from 'dotenv';
import { EnvConfig, envSchema } from '../validation/env.validation';
import { ZodError } from 'zod';
dotenv.config();

export const validateEnv = () => {
try {
const envVars: EnvConfig = envSchema.parse(process.env);
return {
port: +envVars.PORT,
env: envVars.NODE_ENV,
MONGO_DB_URI: envVars.MONGO_DB_URI
};
} catch (error) {
let message = undefined;
if (error instanceof ZodError) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
message = error.errors;
console.error('Validation failed:', error.errors);
} else {
// message = error;
console.error('Error parsing environment variables:', error);
}
}
};


Security

okay for usage lets take the mongoose.ts file as sample, in part one we were using the “process.env.MONGO_DB_URL”. to get our connection string of the mongodb Atlas database. now we can import the validateEnv function from .config/env.config and update the code as below:

import { connect, set } from 'mongoose';
import { validateEnv } from '../config/env.config';

const MONGO_DB_URI = validateEnv().MONGO_DB_URI;

// connection to db
export const connectToDB = async () => {
try {
set('strictQuery', false);
const db = await connect(MONGO_DB_URI);
console.log('MongoDB connected to', db.connection.name);
// Emit an event when the connection is successful
} catch (error) {
console.error(error);
// Emit an event when there's an error
}
};

do the for every access to .env in your project to keep it your .env validated.

Error Handling

Error handling in an Express server is crucial for maintaining the stability and reliability of your application. Express provides mechanisms to handle errors gracefully, ensuring that your server remains responsive even in the face of unexpected issues.

lets create a file called custom.error.ts in the errors folder.

src/error/custom.error.ts

class CustomAPIError extends Error {
message: string
errorCode: ErrorCode
statusCode: number
error: any
constructor(message: string, errorCode: ErrorCode, statusCode: number, error: any) {
super(message);
this.message = message
this.errorCode = errorCode
this.statusCode = statusCode
this.error = error
}
}

export enum ErrorCode {
NOT_FOUND = 1001,
FORBIDDEN = 1003,
INTERNAL_SERVER = 1005,
BAD_REQUEST= 1007,

}
export default CustomAPIError;

CustomAPIError is a custom error class that extends the built-in Error class in JavaScript. It's designed to represent specific errors that can occur in an API. This class has properties such as message, errorCode, statusCode, and error, which provide detailed information about the error.

  • message: A descriptive message explaining the error.
  • errorCode: An enumerated value representing the type of error. This helps in identifying the specific error scenario.
  • statusCode: The HTTP status code associated with the error. This allows the client to understand the severity of the error.
  • error: Additional contextual information about the error, such as a stack trace or details from the underlying error object.

By using this custom error class, you can create instances of CustomAPIError with specific error details, making it easier to handle and communicate errors consistently throughout your application. The ErrorCode enum provides predefined error codes for common error scenarios, improving code readability and maintainability.

now lets make the error handlers, for each scenarios

Bad Request Error Handler (src/error/badRequest.error.ts)

import CustomAPIError, { ErrorCode } from "./custom.errors";

class BadRequestError extends CustomAPIError {
statusCode: number;
constructor(message: string, errorCode: ErrorCode) {
super(message, errorCode, 400, null);
this.statusCode = 400;
}
}

export default BadRequestError;

Not Found Error Handler(src/errors/notFound.error.ts)

import CustomAPIError, { ErrorCode } from "./custom.errors";

class NotFoundError extends CustomAPIError {
statusCode: number;
constructor(message: string, errorCode: ErrorCode) {
super(message, errorCode, 404, null);
this.statusCode = 404;
}
}

export default NotFoundError;

Forbidden Error Handler(src/errors/forbidden.ts)

import CustomAPIError, { ErrorCode } from "./custom.errors";

class ForbiddenError extends CustomAPIError {
statusCode: number;
constructor(message: string, errorCode: ErrorCode) {
super(message, errorCode, 403, null);
this.statusCode = 403;
}
}

export default ForbiddenError;

Internal Server Error Handler(src/errors/internalServer.error.ts)

import CustomAPIError, { ErrorCode } from "./custom.errors";

class InternalserverError extends CustomAPIError {
statusCode: number;
constructor(message:string, errorCode: ErrorCode) {
super(message, errorCode, 500, null);
this.statusCode = 500;
}
}

export default InternalserverError;

okay finally create a file called index.error.ts to export all this error handlers and use the by importing them from one file through out our project

import BadRequestError from './badRequest.errors'
import ForbiddenError from './forbidden.errors'
import NotFoundError from './notFound.errors'
import InternalSeverError from './internalServer.errors'

export { BadRequestError, ForbiddenError, NotFoundError, InternalSeverError}

okay now that we have our errorHandlers, we have to create our error handler middleware. lets create a filed called “errorHandler.middleware.ts ”in the middleware folder inside src.

src/middleware/errorHandler.middleware.ts

import { NextFunction, Request, Response } from "express";
import CustomAPIError from "../errors/custom.errors";

const errorHandlerMiddleware = (err: any, req: Request, res: Response, next: NextFunction) => {
const defaultError = {
statusCode: err.statusCode || 500,
msg: err.message || "Something went wrong, try again later",
};
if (err instanceof CustomAPIError) {
return res
.status(defaultError.statusCode)
.json({ message: defaultError.msg, sucess: false });
}
if (err.name === "ValidationError") {
defaultError.statusCode = 500;
defaultError.msg = Object.values(err.errors)
.map((item: { message: string }) => item?.message)
.join(",");
}
if (err.name = 'CastError') {
defaultError.statusCode = 400;
defaultError.msg = `Resourse not found. Invalid :${err.path}`;
}

if (err.code && err.code === 11000) {
defaultError.statusCode = 400;
defaultError.msg = `${Object.keys(err.keyValue)} field has to be unique`;
}
res
.status(defaultError.statusCode)
.json({ message: defaultError.msg, sucess: false });
};

export default errorHandlerMiddleware;

Here’s a summarized explanation:

- The middleware function takes four parameters: `err`, `req`, `res`, and `next`. It catches errors that occur during request processing.

- It defines a default error object with a status code and a message to be sent back to the client.

- If the error is an instance of `CustomAPIError`, it sends a JSON response with the specified status code and message.

- If the error is a `ValidationError`, it sets the status code to 500 and constructs a message from the validation errors.

- If the error is a `CastError`, it sets the status code to 400 and constructs a message indicating a resource not found due to an invalid parameter.

- If the error has a code of 11000 (indicating a duplicate key error from MongoDB), it sets the status code to 400 and constructs a message indicating which field must be unique.

- Finally, it sends a JSON response with the default error status code and message.

This middleware handles different types of errors gracefully, providing appropriate responses to clients based on the nature of the error. It helps in maintaining consistency and clarity in error handling throughout the application.

Security

In the dynamic landscape of web development, creating applications that are not only functional but also secure is paramount. Express, a minimalist web framework for Node.js, combined with MongoDB, a powerful NoSQL database, forms a robust foundation for building modern web applications. However, ensuring that your application is secure from potential vulnerabilities is crucial to protect sensitive data and maintain user trust.

In this blog post, we’ll explore the essentials of building secure applications using Express and MongoDB. We’ll delve into best practices for data validation, and protection against common web vulnerabilities such as SQL injection, cross-site scripting (XSS), and cross-site request forgery (CSRF). By the end of this guide, you’ll have a solid understanding of how to implement security measures that safeguard your Express and MongoDB application, ensuring a safe and reliable user experience.

express-mongo-sanitize is a middleware for Express.js, a popular web framework for Node.js applications, designed to sanitize user-supplied data to prevent MongoDB operator injection attacks.

npm install express-mongo-sanitize

on app.ts file inside src, import and use it

import ExpressMongoSanitize from "express-mongo-sanitize";
app.use(ExpressMongoSanitize());

helmet middleware in an Express.js application to enhance security by setting various HTTP headers

 npm i helmet
app.use(helmet());
app.use(helmet.crossOriginResourcePolicy({ policy: "cross-origin" }));
app.use(helmet.xssFilter());
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'trusted-cdn.com'"],
objectSrc: ["'none'"],
upgradeInsecureRequests: [],
},
}));

This code snippet shows the usage of the helmet middleware in an Express.js application to enhance security by setting various HTTP headers. Here's a breakdown of each helmet function being used:

  1. helmet(): This middleware sets a variety of HTTP headers to improve security by mitigating common web vulnerabilities. It includes headers such as X-Content-Type-Options, X-Frame-Options, and Strict-Transport-Security by default.
  2. helmet.crossOriginResourcePolicy(): This middleware sets the Cross-Origin Resource Policy (CORP) header, which controls whether and how resources on a web page can be requested from another origin. In this example, the policy is set to "cross-origin", allowing cross-origin requests.
  3. helmet.xssFilter(): This middleware enables the Cross-Site Scripting (XSS) filter in the browser by setting the X-XSS-Protection header. This header helps prevent XSS attacks by instructing the browser to block pages that contain suspicious code.
  4. helmet.contentSecurityPolicy(): This middleware sets the Content Security Policy (CSP) header, which helps prevent various types of attacks, such as XSS and data injection attacks, by specifying the allowed sources for various types of content (e.g., scripts, stylesheets, images). In this example, the CSP is configured to allow scripts only from the same origin ('self') and from a trusted CDN ('trusted-cdn.com'). The objectSrc directive is set to "none", meaning no plugins or embedded content are allowed, and upgradeInsecureRequests is an empty array, indicating that the browser should upgrade insecure requests to HTTPS.

By using these helmet middleware functions, you enhance the security of your Express.js application by setting various HTTP headers that help prevent common web vulnerabilities and protect against attacks.

cors — The cors middleware in Express.js is used to enable Cross-Origin Resource Sharing (CORS), which allows controlled access to resources located on a different origin domain

lets create a new file called corsOptions.ts inside the config folder, and past the snippet of code below.

import cors from 'cors'
const allowedOrigins: string[] =
[
"http://localhost:3000/",
"http://localhost:3001/",
"http://localhost:3002/",
"http://localhost:3003/",

];

export const corsOptions: cors.CorsOptions = {
origin: (origin: string | undefined, callback: (error: Error | null, allow?: boolean) => void) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (allowedOrigins.indexOf(origin!) !== -1 || !origin) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
},
methods: ["GET", "POST", "PATCH", "DELETE", "PUT"],
credentials: true,
optionsSuccessStatus: 200,
};

This code sets up CORS (Cross-Origin Resource Sharing) configuration for an Express.js application using the cors middleware. Here's a summarized explanation:

  • Importing cors: The cors middleware is imported from the cors package.
  • Allowed Origins: An array named allowedOrigins is defined, containing a list of origins (URLs) that are allowed to access the server's resources. These origins typically represent different instances of your frontend application running on different ports during development.
  • corsOptions Object: The corsOptions object defines the CORS configuration options:
  • origin: A function that checks whether the requesting origin is included in the allowedOrigins array. If the origin is allowed or if no origin is provided (which is typically the case for same-origin requests), the callback is called with null as the error and true as the second argument, indicating that the request is allowed. Otherwise, an error is passed to the callback, indicating that the request is not allowed.
  • methods: An array specifying the HTTP methods (e.g., GET, POST, PATCH) that are allowed for CORS requests.
  • credentials: A boolean indicating whether to include credentials (such as cookies or HTTP authentication) in CORS requests. Setting this to true allows credentials to be sent with cross-origin requests.
  • optionsSuccessStatus: The HTTP status code to be sent for successful CORS preflight requests (OPTIONS requests).

Usage: The corsOptions object can be passed as an argument to the cors middleware to configure CORS for the Express.js application on the app.ts file:

import cors from "cors";
import { corsOptions } from "../config/corsOption";
app.use(cors(corsOptions));

okay by now your app.ts file should look like this

import { config } from "dotenv";
import express from "express";
import morgan from "morgan";
import helmet from "helmet";
import cors from "cors";
import bodyParser from "body-parser";
import cookieParser from "cookie-parser";
import api from "./api/index.api"
import { notFoundMiddleware, errorHandlerMiddleware } from "./middleware/index.middleware";
import ExpressMongoSanitize from "express-mongo-sanitize";
import { corsOptions } from "./config/corsOption";

config();

const app = express();

app.use(ExpressMongoSanitize());
app.use(morgan("dev"));
app.use(helmet());
app.use(helmet.crossOriginResourcePolicy({ policy: "cross-origin" }));
app.use(helmet.xssFilter());
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'trusted-cdn.com'"],
objectSrc: ["'none'"],
upgradeInsecureRequests: [],
},
}));
app.use(cors());
app.use(express.json());
app.use(cors(corsOptions));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true, limit: "30mb" }));

app.use("/api/", api);

app.use(notFoundMiddleware);
app.use(errorHandlerMiddleware);

export default app;

Logging

we are going to use morgna to log the request made to our express server

  • Morgan: Morgan is a middleware for logging HTTP requests in an Express.js server. It helps in logging details of incoming requests to your server, which is useful for debugging and monitoring

let create a file called morgan.ts in the config folder.

src/config/morgan.ts

import { createWriteStream } from 'fs';
import morgan from 'morgan';
import path from 'path';
import { validateEnv } from './env.config';

const nodeEnv = validateEnv()?.env
const getIPFormat = () =>
nodeEnv === 'production' ? ':remote-addr - ' : '';

const accessLogStream = createWriteStream(
path.join(__dirname, "..", 'logs/access.log'),
{ flags: 'a' }
);

const successResponseFormat = `${getIPFormat()} :method :url :status :response-time ms :user-agent :date`;
const successHandler = morgan(successResponseFormat, {
stream: accessLogStream,
skip: (req, res) => res.statusCode >= 400,
});

const errorResponseFormat = `${getIPFormat()} :method :url :status :response-time ms :user-agent :date `;
const errorHandler = morgan(errorResponseFormat, {
stream: accessLogStream,
skip: (req, res) => res.statusCode < 400,
});

export { errorHandler, successHandler }

This code sets up logging middleware using morgan in an Express.js application. Here's a summarized explanation:

  • Importing Modules: The code imports necessary modules such as createWriteStream from fs, morgan, path, and validateEnv from the local config module.
  • Environment Validation: It checks the environment variable using validateEnv()?.env to determine whether the application is running in production mode.
  • Logging Configuration:
  • getIPFormat: This function returns the IP format string based on the environment. In production, it includes the remote address (:remote-addr - ), and in other environments, it returns an empty string.
  • accessLogStream: This creates a writable stream to the access log file located at 'logs/access.log', appending data to the file ({ flags: 'a' }).
  • Morgan Middleware Configuration:
  • successResponseFormat: Defines the log format for successful requests, including method, URL, status code, response time, user agent, and date.
  • errorResponseFormat: Defines the log format for error requests, similar to the success format.
  • successHandler: Sets up the morgan middleware for logging successful requests. It writes logs to the accessLogStream stream, skipping requests with status codes greater than or equal to 400.
  • errorHandler: Sets up the morgan middleware for logging error requests. It also writes logs to the accessLogStream stream, skipping requests with status codes less than 400.
  • Exporting Middleware: Finally, the code exports the errorHandler and successHandler middleware functions to be used in the Express.js application.

In summary, this code configures morgan middleware to log HTTP requests in a specified format to a file (logs/access.log). It separates logs for successful and error requests and allows customization based on the environment.

IMPORTANT: lets create a folder called logs and a file inside it called access.log

Usage: in side our app.ts file, lets import errorHandler and successHandler use them just like the snippet below:

import { config } from "dotenv";
import express from "express";
import morgan from "morgan";
import helmet from "helmet";
import cors from "cors";
import bodyParser from "body-parser";
import cookieParser from "cookie-parser";
import api from "./api/index.api"
import { notFoundMiddleware, errorHandlerMiddleware } from "./middleware/index.middleware";
import ExpressMongoSanitize from "express-mongo-sanitize";
import { corsOptions } from "./config/corsOption";
import { errorHandler, successHandler } from "./config/morgan";

config();

const app = express();

app.use(successHandler);
app.use(errorHandler);
app.use(ExpressMongoSanitize());
app.use(morgan("dev"));
app.use(helmet());
app.use(helmet.crossOriginResourcePolicy({ policy: "cross-origin" }));
app.use(helmet.xssFilter());
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'trusted-cdn.com'"],
objectSrc: ["'none'"],
upgradeInsecureRequests: [],
},
}));
app.use(cors());
app.use(express.json());
app.use(cors(corsOptions));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true, limit: "30mb" }));
app.use("/api/", api);
app.use(notFoundMiddleware);
app.use(errorHandlerMiddleware);

export default app;

okay now lets add another logger packge called Winston

Winston is a popular logging library for Node.js applications, including Express servers. It provides a flexible and extensible logging framework with support for multiple transports (e.g., console, file, database) and customizable logging formats. Here’s an overview of how Winston is typically used in an Express server:

install the package using the command below:

npm install winston

in the config folder lest create a file called logger.ts, and paste the snippet of code below:

import winston from "winston"
import { validateEnv } from "./env.config";
const { format, createLogger, transports } = winston;
const { printf, combine, timestamp, colorize, uncolorize } = format;

const nodeEnv = validateEnv()?.env
const winstonFormat = printf(({ level, message, timestamp, stack }) => {
return `${timestamp}: ${level}: ${stack || message}`;
});
export const logger = createLogger({
level: nodeEnv === 'development' ? 'debug' : 'info',
format: combine(
timestamp(),
winstonFormat,
nodeEnv === 'development' ? colorize() : uncolorize()
),
transports: [new transports.Console()],
});

This code snippet sets up a logging configuration using the Winston library in a Node.js application, likely an Express server. Here’s a summary of what it does:

Importing Winston and Environment Configuration:

  • The code imports the Winston library and the validateEnv function from a local module, likely for environment configuration.

Destructuring Variables:

  • It destructures variables from the Winston library, including format, createLogger, and transports.

Defining Log Format:

  • A custom log format is defined using the printf function from Winston's format. This format includes the timestamp, log level, and optionally the stack trace or message.

Creating Logger Instance:

  • The createLogger function is called to create a logger instance with the specified configuration:
  • The log level is set based on the environment (debug in development, info in other environments).
  • The log format includes a timestamp and custom format defined earlier. In development mode, log messages are colorized for better readability.
  • Only a console transport is configured, meaning logs will be output to the console.

Exporting Logger:

  • The logger instance is exported as logger, allowing other modules in the application to import and use it for logging.

In summary, this code sets up a basic logging configuration using Winston, defining a custom log format and configuring a logger instance with console transport. It adapts the logging behavior based on the environment, providing more detailed logs in development mode.

okay now lets bootstrap the server running files such as app.ts and index.ts

First lets go to out src folder and create a folder called loader and move the file app.ts to the loader folder and make the changes below

src/loader/app.ts

import { config } from "dotenv";
import express from "express";
import morgan from "morgan";
import helmet from "helmet";
import cors from "cors";
import bodyParser from "body-parser";
import cookieParser from "cookie-parser";
import api from "../api/index.api"
import { notFoundMiddleware, errorHandlerMiddleware } from "../middleware/index.middleware";
import ExpressMongoSanitize from "express-mongo-sanitize";
import { corsOptions } from "../config/corsOption";
import { errorHandler, successHandler } from "../config/morgan";
config();
export const bootstrapExpress = (app: any) => {
app.use(successHandler);
app.use(errorHandler);
app.use(ExpressMongoSanitize());
app.use(morgan("dev"));
app.use(helmet());
app.use(helmet.crossOriginResourcePolicy({ policy: "cross-origin" }));
app.use(helmet.xssFilter());
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'trusted-cdn.com'"],
objectSrc: ["'none'"],
upgradeInsecureRequests: [],
},
}));
app.use(cors());
app.use(express.json());
app.use(cors(corsOptions));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true, limit: "30mb" }));
app.use("/api/", api);
app.use(notFoundMiddleware);
app.use(errorHandlerMiddleware);
}

lets create another file inside the loader folder called bootstrap.ts and paste the snippet of code below:

import { bootstrapExpress } from "./app";
import { logger } from '../config/logger';
import { validateEnv } from "../config/env.config";
import { connectToDB } from "../config/mongoose";

export const bootstrap = async (app) => {
validateEnv()
await connectToDB();
bootstrapExpress(app);
logger.info('Express app initiated.')

};

then on our index.ts file located on root directory of our project lets change it as following:

import express, { Express } from "express";
import { Server, createServer } from 'http';
import { logger } from './src/config/logger';
import { validateEnv } from "./src/config/env.config"
import mongoose from "mongoose";
import { bootstrap } from "./src/loader";


const exitHandler = (server: Server | null) => {
if (server) {
server.close(async () => {
logger.info('Server closed');
process.exit(1);
});
} else {
process.exit(1);
}
};

const unExpectedErrorHandler = (server: Server) => {
return function (error: Error) {
logger.error(error);
exitHandler(server);
};
};

const startServer = async () => {
const app: Express = express();
await bootstrap(app);

const httpServer = createServer(app);
const port = validateEnv().port

const server: Server = httpServer.listen(port, () => {
logger.info(`server listening on port ${port}`);
});

process.on('uncaughtException', unExpectedErrorHandler(server));
process.on('unhandledRejection', unExpectedErrorHandler(server));
process.on('SIGTERM', () => {
logger.info('SIGTERM recieved');
if (server) {
server.close();
}
});

mongoose.connection.on("error", (err) => {
console.log(`${err.no}: ${err.code}\t${err.syscall}\t${err.hostname}`);
});
};

startServer();

This code sets up an Express.js server with error handling and starts it listening on a specified port. Here’s a summary of what it does:

  1. Importing Dependencies:
  • The code imports necessary modules including `express` for creating the Express app, `http` for creating the HTTP server, `mongoose` for interacting with MongoDB, and custom modules such as `logger` and `validateEnv`.

2. Exit Handler Function:

  • Defines an `exitHandler` function responsible for closing the server gracefully when the process exits.

3. Unexpected Error Handler Function:

  • Defines an `unExpectedErrorHandler` function that logs unexpected errors and calls the `exitHandler` to close the server.

4. Start Server Function:

  • Defines an `async` function `startServer` responsible for setting up the Express app, bootstrapping it, creating an HTTP server, and listening on a specified port.
  • Registers event handlers for uncaught exceptions, unhandled rejections, and SIGTERM signals to gracefully shut down the server.

5. Express App Setup and Bootstrapping:

  • Creates an instance of the Express app (`app`).
  • Calls the `bootstrap` function (presumably initializing middleware, routes, etc.) to set up the Express app.

6. Creating HTTP Server:

  • Creates an HTTP server using `createServer` from the `http` module, passing the Express app as a callback.

7. Listening on Port:

  • Retrieves the port number from the environment configuration using `validateEnv().port`.
  • Calls `httpServer.listen` to start the server listening on the specified port.

8. Error Handling:

  • Registers event listeners for `uncaughtException`, `unhandledRejection`, and `SIGTERM` signals to handle unexpected errors and server shutdown gracefully
  • If there’s a MongoDB connection error, it logs the error details.

9. Starting the Server:

  • Calls the `startServer` function to begin the server initialization process.

In summary, this code sets up an Express.js server with proper error handling, including handling unexpected errors and gracefully shutting down the server. It also includes MongoDB connection error handling and starts the server listening on a specified port.

okay finally lets check if our application runs perfectly, lets open our terminal and run the command npm run dev

Okay its working just as we want it, congrats we have finished setup our node js applications.

You can find what we have done so far on my git repository on branch starter-template here

Part Three: User Authentication using JWT Access Token (HERE)

Thankyou
Ermias Asmare

--

--