DEV Community

T.M
T.M

Posted on • Edited on

CUSTOM NODE JS ERROR HANDLING

Introduction

How we present errors and other response messages to the client determines the ease with which they can handle them. Assume that you are using microservice architecture and different stacks are being used for backend development.
If all the endpoints will be handled using a single frontend client then there is a dire need for uniformity in the way we pass error responses.This will make the frontend developers to come up with a single error handling method.

In this article I will be showing you how to handle and pass error responses to the frontend client using typescript, Node js and express js.

All our error responses will be in the following format:

{
  "errors": [
    {
      "message": "Description",
      "field": "referenced field eg Email"
    },
    {
      "message": "Description ",
      "field": "referenced field eg name"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Initializing the project

  • Create a new project directory and open the directory on your terminal.
mkdir error-handling-example
Enter fullscreen mode Exit fullscreen mode
  • Change into the newly created directory
cd error-handling-example
Enter fullscreen mode Exit fullscreen mode
  • If you have installed node js on your machine, run the following command to initialize the project:
npm init -y
Enter fullscreen mode Exit fullscreen mode

This command will create a package.json file.

  • Run the following commands to install the necessary packages:
npm install -D typescript
npm install -D tslint

Enter fullscreen mode Exit fullscreen mode

The -D flag is the shortcut for: --save-dev
The above two commands will install typescript and tslint. tslint will be used to establish coding rules on your project.
Add the following two commands:

npm install -S express
npm install -D @types/express
npm install -D express-async-errors
npm install -D ts-node-dev
npm install express-validator

Enter fullscreen mode Exit fullscreen mode

On your terminal, run the following command to initialize the typescript project configurations:

tsc --init
Enter fullscreen mode Exit fullscreen mode

This will create a tsconfig.json file on your project root directory.
On the root of your project create a new directory src/ and in it a file app.ts

  • Open the application on your favourite IDE.

Create the following folders and files inside your src directory to match the structure below

File structure

Creating the server

Add the following code to the app.ts file

import express from "express";
import "express-async-errors";
import { NotFoundError } from "./errors/not-found-err";
import { errorHandler } from "./middlewares/errors-handler";

import authentication from "./routes/routes";

const app = express();
app.use("/api/users", authentication);
app.all("*", async () => {
  throw new NotFoundError();
});
app.use(errorHandler);

const port = 3000;
app.listen(port, () => {
  console.log(`server is listening on ${port}`);
});
Enter fullscreen mode Exit fullscreen mode

adds the asynchronous error handling for express js:

import "express-async-errors";
Enter fullscreen mode Exit fullscreen mode

On the file routes/routes.ts file paste the following piece of code:

import { signUp } from "../controllers/signup";
import express from "express";
import { body } from "express-validator";

const router = express.Router();

router
  .route("/signup")
  .post(
    [
      body("email").isEmail().withMessage("Invalid email"),
      body("password")
        .trim()
        .isLength({ min: 6, max: 20 })
        .withMessage("Password must be between 6 to 20 characters "),
    ],
    signUp
  );

export default router;
Enter fullscreen mode Exit fullscreen mode

As you have noticed this file is imported on the app.ts file as

import authentication from "./routes/routes";
Enter fullscreen mode Exit fullscreen mode

and is uses for route handling on

app.use("/api/users", authentication);
Enter fullscreen mode Exit fullscreen mode

in the app.ts file.

Handling the controllers

On the controllers/signup.ts file add the following code.

import { Request, Response } from "express";
import { RequestValidationError } from "../errors/requests-validation-errors";
import { validationResult } from "express-validator";

export const signUp = async (req: Request, res: Response) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    throw new RequestValidationError(errors.array());
  }
  const { email, password } = req.body;
  res.status(201).send({
    email: email,
    password: password,
  });
};
Enter fullscreen mode Exit fullscreen mode

On this function you can handle different situations including storigng the user to the database, generating a new token , an encrypted password or validate the imputs further.

Creating Custom error class

Create an abstract custom error class which extends the Error functions.

  • This class will impose a statusCode and an error serializer function to any other class that extends it.

  • NB) Abstract classes can only be extended and not instantiated

The class should look as below

export abstract class CustomError extends Error {
  abstract statusCode: number;
  constructor(message: string) {
    super(message);
    Object.setPrototypeOf(this, CustomError.prototype);
  }
  abstract serializeErrors(): { message: string; field?: string }[];
}
Enter fullscreen mode Exit fullscreen mode

In the same folder errors add the following sample code on the requests-validation.ts file:

import { ValidationError } from "express-validator";
import { CustomError } from "./custom-error";

export class RequestValidationError extends CustomError {
  statusCode = 400;
  constructor(public errors: ValidationError[]) {
    super("Invalid request params");
    Object.setPrototypeOf(this, RequestValidationError.prototype);
  }
  serializeErrors() {
    return this.errors.map((err) => {
      return { message: err.msg, field: err.param };
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

The RequestValidationError class extends the CustomError class created above and therefore must implement a serializeErrors method and statusCode must be instantiated here.

To use the RequestValidationError class on the signup method, add the following code to the middlewares/error-handlers.ts file

import { Request, Response, NextFunction } from "express";
import { CustomError } from "../errors/custom-error";

export const errorHandler = (
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) => {
  if (err instanceof CustomError) {
    return res.status(err.statusCode).send({ errors: err.serializeErrors() });
  }
  res.status(400).send({
    errors: [{ message: "Something went wrong" }],
  });
};
Enter fullscreen mode Exit fullscreen mode

This part of the signup function can be extracted into a middleware since it will be called many times when a validation is being conducted.

const errors = validationResult(req);
if (!errors.isEmpty()) {
  throw new RequestValidationError(errors.array());
}
Enter fullscreen mode Exit fullscreen mode

If extracted pass it as the second argument on the post method of the route you want to validate i.e:


.post([validation rules], place-the-extracted-function-here,()=>{})

Enter fullscreen mode Exit fullscreen mode

Fire up the terminal and one the project root folder run


npm start

Enter fullscreen mode Exit fullscreen mode

Your project will start on http://localhost:3000.

You can try out the project using postman:

This is my first article here.
Please don't forget to leave your comment on any improvements that can be done to the article.

Top comments (0)