Error Handling in Express

Error Handling in Express

Error handling in Nodejs can be a bit tricky especially if you want to go outside the conventional try-catch method. In this article, I am going to be sharing how using the Error Constructor and middlewares errors can be handled and sent to the user in human-readable formats.

Async wrapper:

Async wrapper helps us handle errors and parse errors to the next middleware that expects an error value/object. The async wrapper uses currying in Javascript to access the internal controller methods like req, res, next

Whatever is inside the asyncWrapper function is run as a function and the values of req, res, next parsed into the function to run as a normal controller.

const asyncWrapper = (fn) => {
  return async (req, res, next) => {
    try {
      await fn(req, res, next)
    } catch (error) {
      next(error)
    }
  }
}

module.exports = asyncWrapper

Creating the Controller

Assume we have a controller for a todo app.

Create a controller for getting tasks and parsing in the normal controller (req, res,next)=>{} as an argument to the asyncWrapper function.

const Task = require('models/Task')
const { createCustomError } = require('errors/custom-error')

const getTask = asyncWrapper(async (req, res, next) => {
  const { id: taskID } = req.params
  const task = await Task.findOne({ _id: taskID })
  if (!task) {
    return next(createCustomError(`No task with id : ${taskID}`, 404))
  }

  res.status(200).json({ task })
})

Custom API Error

We can extend the Error Object and create a CustomError that accepts the message and status code in the constructor. The message args in the constructor is native to the Error object. With this CustomAPIError we can parse parameters to the class and receive them in middleware to send data to the user as shown below this section.

class CustomAPIError extends Error {
  constructor(message, statusCode) {
    super(message)
    this.statusCode = statusCode
  }
}

const createCustomError = (msg, statusCode) => {
  return new CustomAPIError(msg, statusCode)
}

Create a function createCustomError to instantiate the CustomAPIError.

We can now easily dispatch this error in our controller as seen above by parsing the necessary parameters to the function and returning them and using the next function as a dispatch to next middleware that accepts error.

if (!task) {
    return next(createCustomError(`No task with id : ${taskID}`, 404))
  }

Time to catch the errors

In the app.js we write the code below, the error, the app.use function serves as a listener each time the next function with createCustomError args is parsed into it.

app.use(errorHandlerMiddleware);

The error handler middleware function:

This function takes in any error argument that is parsed to the next(error) in the middleware function, it is parsed down as a tree and this middleware sort of like catches the and returns various status codes.

Usually, we check for the instance of the error to affirm if it is from our CustomAPIError, if it is from our CustomAPIError we can now access the constructor arguments statusCode and message . And send the message from and status code.

Else if it is not from our CustomAPIError, then it means the error isn't from the developer hence we send a status code of 500 to the user since at the moment we can't account for the error.

const { CustomAPIError } = require('errors/custom-error')
const errorHandlerMiddleware = (err, req, res, next) => {
  if (err instanceof CustomAPIError) {
    return res.status(err.statusCode).json({ msg: err.message })
  }
  return res.status(500).json({ msg: 'Something went wrong, please try again' })
}

module.exports = errorHandlerMiddleware

Viola! Our middleware is ready. We can now put in the app.js to catch an error we send to the middleware in our app.js

Conclusion:

While this works perfectly fine and causes less repetition of try-catch it can be considered to be over-engineering of code and making file structure so nested. Depending on the size and scale of the Project, I recommend try-catch for small projects, and in Big Projects this approach. I am also writing part 2 of this article for the Advanced handling of errors in express applications.

Credits to Coding Addict on youtube for explaining these concepts in my early days of Node/Express.

Attached here is a project where I implemented this flow: Express Error Project