How to Handle Errors In Express.js

Table of contents

No heading

No headings in the article.

Errors in Express.js refer to any problems that occur while processing a request or response. These errors can be caused by a variety of factors, such as runtime errors, database errors, validation errors, and more. I'll be discussing the handling of a "404, Route Not Found Error" and "Passed Down Error" in this article.

404, Route Not Found Error

This is an error that results from a request made to an unknown route on your API. Express automatically catches this error and sends back a response for you without crashing the server, but we'd prefer a custom response to the generic one, and here is how to achieve that:

Express sends back this response when a request is made to an unknown or a route not defined on your API :

But we prefer our custom error response, which is clearer and easier to understand, here is an example of a response we prefer:

To send back a custom error response for not found routes, we need to include this middleware at the end of all routes in our index/app.js (depending on your entry point) to avoid valid routes being treated as an error route:

// Handles error from an unknown route
app.use('*', (req, res) => {
  return res.status(404).json({ message: 'route not found' })
})

It should be added after all the available routes have been defined. For example:

With those few steps, we have been able to create a custom response to not found routes in express.js.

Pass Down Error

These are the most common type of errors in Express.js, they are usually unhandled errors from try-and-catch blocks or if statements which lead to them propagating up the call stack and causing our program to crash. These types of errors are not handled by Express.js which means we need to define an error-handling middleware to catch and handle them when they occur.

An example of this error might result from this block below:


    const user = await UserModel.create(req.body)
    const token = createToken(user._id)
    res.status(201).json({message: 'Signup successful', user, token})

If a user tries to sign up with an email already saved in the database and the database model was defined to be for unique emails only, an error will result from this request, which will lead to a crash in our program.

To prevent these types of errors, we wrap our code in a try-and-catch block to ensure that our server keeps running and that the right response is sent.

 try{
    const user = await UserModel.create(req.body)
    const token = createToken(user._id)
    res.status(201).json({message: 'Signup successful',user, token})
  }
  catch(err){
    //checks if the type of error is due to duplicate values or not
    if (err.code === 11000) {
      next({
      status:400,
        message: "Email already exists"
      });
    }
    else{
      next(err)
    }
  }

We know that the error code for duplicate values in MongoDB is "11000", therefore we can check if the resulting error is a result of duplicate values or not, to allow us to send more specific and precise responses.

With the error passed into the next(), we can finally handle all errors from the next() in our error-handling middleware in the app.js. Here is how to define error-handling middleware:

app.use((err, req, res, next) => {
    console.log(err)
    //check if an error status code was sent or not
    const errorStatus = err.status || 500
    //sends a response
    res.status(errorStatus).send(err.message)
    next()
})

We need to also place this block of code at the end of our app/index.js to ensure that all errors are properly caught and handled.

With those few steps, we have been able to handle most of the errors that might lead to a crash in our server. To learn more, visit the official Express Documentation