In Node.js, there are various ways to handle errors. In this blog, I will show you how to handle them in a very easy way and get rid of all try-catch blocks.
Setup
Let’s assume we have a simple Node.js (Express) server running. Now install npm packages called express-async-errors and http-status-codes.
npm i express-async-errors
express-async-errors is a small package which does most of the heavy lifting. Let’s use this package in the root file, and it should always be at the top.
require('express-async-errors');
const express = require('express');
const app = express();
app.use(express.json());
const port = process.env.PORT || 5000;
app.listen(port, () => {
console.log(`server is listening on port ${port}...`);
});Custom Error Utility
Let’s create utility functions to throw different errors.
const { StatusCodes } = require("http-status-codes");
class CustomAPIError extends Error {
constructor(message) {
super(message);
}
}
class BadRequestError extends CustomAPIError {
constructor(message) {
super(message);
this.statusCode = StatusCodes.BAD_REQUEST;
}
}
class ConflictError extends CustomAPIError {
constructor(message) {
super(message);
this.statusCode = StatusCodes.CONFLICT;
}
}
class NotFoundError extends CustomAPIError {
constructor(message) {
super(message);
this.statusCode = StatusCodes.NOT_FOUND;
}
}
class UnauthenticatedError extends CustomAPIError {
constructor(message) {
super(message);
this.statusCode = StatusCodes.UNAUTHORIZED;
}
}
class UnauthorizedError extends CustomAPIError {
constructor(message) {
super(message);
this.statusCode = StatusCodes.FORBIDDEN;
}
}
module.exports = {CustomAPIError, BadRequestError, ConflictError, NotFoundError, UnauthenticatedError, UnauthorizedError}Middleware
const { StatusCodes } = require('http-status-codes');
const { CustomAPIError } = require('../errors');
const errorHandlerMiddleware = (err, req, res, next) => {
const customError = {
msg: 'Something went wrong, please try again',
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
};
if (err instanceof CustomAPIError) {
customError.msg = err.message;
customError.statusCode = err.statusCode;
}
res.status(customError.statusCode).json({ msg: customError.msg });
};
module.exports = errorHandlerMiddleware;Optional Checks
You can add some optional checks too. I will show some examples for Prisma and Mongoose.
Prisma
const { StatusCodes } = require('http-status-codes');
const { Prisma } = require('@prisma/client');
const { CustomAPIError } = require('../errors');
const errorHandlerMiddleware = (err, req, res, next) => {
const customError = {
msg: 'Something went wrong, please try again',
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
};
if (err instanceof CustomAPIError) {
customError.msg = err.message;
customError.statusCode = err.statusCode;
}
if (err instanceof Prisma.PrismaClientKnownRequestError) {
switch (err.code) {
case 'P2000': {
const key = err.meta.column_name;
customError.msg = `${key} is too long`;
customError.statusCode = StatusCodes.BAD_REQUEST;
break;
}
case 'P2001': {
customError.msg = 'Not found';
customError.statusCode = StatusCodes.NOT_FOUND;
break;
}
case 'P2002': {
const key = err.meta.target[0];
customError.msg = `Provided ${key} already exists`;
customError.statusCode = StatusCodes.CONFLICT;
break;
}
case 'P2003': {
const key = err.meta.field_name;
customError.msg = `${key} does not exist`;
customError.statusCode = StatusCodes.NOT_FOUND;
break;
}
case 'P2025': {
customError.msg = 'No record found to delete';
customError.statusCode = StatusCodes.NOT_FOUND;
break;
}
default: {
customError.msg = 'Something went wrong, please try again';
customError.statusCode = StatusCodes.INTERNAL_SERVER_ERROR;
}
}
}
res.status(customError.statusCode).json({ msg: customError.msg });
};
module.exports = errorHandlerMiddleware;Mongoose
const { StatusCodes } = require('http-status-codes');
const mongoose = require('mongoose');
const { CustomAPIError } = require('../errors');
const errorHandlerMiddleware = (err, req, res, next) => {
const customError = {
msg: 'Something went wrong, please try again',
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
};
if (err instanceof CustomAPIError) {
customError.msg = err.message;
customError.statusCode = err.statusCode;
}
if (err instanceof mongoose.Error.ValidationError) {
if (err.name === "ValidationError") {
customError.msg = Object.values(err.errors)
.map((item) => item.message)
.join(",");
customError.statusCode = StatusCodes.BAD_REQUEST;
}
if (err.code && err.code === 11000) {
customError.msg = `Duplicate value entered for ${Object.keys(
err.keyValue
)} field, please choose another value`;
customError.statusCode = StatusCodes.BAD_REQUEST;
}
if (err.name === "CastError") {
customError.msg = `No match found with id of ${err.value}`;
customError.statusCode = StatusCodes.NOT_FOUND;
}
}
res.status(customError.statusCode).json({ msg: customError.msg });
};
module.exports = errorHandlerMiddleware;That’s It
Now use that middleware below all API routes. We don’t have to wrap any async code block inside try-catch. All the errors will be automatically caught by our middleware.
require('express-async-errors');
const express = require('express');
const errorHandlerMiddleware = require('./middleware/error-handler');
const app = express();
app.use(express.json());
/* api routes */
...
app.use(errorHandlerMiddleware);
const port = process.env.PORT || 5000;
app.listen(port, () => {
console.log(`server is listening on port ${port}...`);
});Hope that helps! Let me know if you have any further questions or need more clarification!
