Node.js Error Handling

Content Protection by DMCA.com

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!

Content Protection by DMCA.com