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"
}
]
}
Initializing the project
- Create a new project directory and open the directory on your terminal.
mkdir error-handling-example
- Change into the newly created directory
cd error-handling-example
- If you have installed node js on your machine, run the following command to initialize the project:
npm init -y
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
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
On your terminal, run the following command to initialize the typescript project configurations:
tsc --init
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
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}`);
});
adds the asynchronous error handling for express js:
import "express-async-errors";
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;
As you have noticed this file is imported on the app.ts file as
import authentication from "./routes/routes";
and is uses for route handling on
app.use("/api/users", authentication);
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,
});
};
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 }[];
}
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 };
});
}
}
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" }],
});
};
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());
}
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,()=>{})
Fire up the terminal and one the project root folder run
npm start
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)