How to add Custom Error Handler Middleware to your Node.js/Express API

A robust, functional web application requires many different components and handling errors is one of them. To avoid a poor user experience that could hurt your business, it is essential to ensure that your application failsafe by accounting for all potential errors and exceptions.

Besides the excellent user experience, custom error-handling can give you important information about potential issues within your program that could crash the whole thing and allow you to track the bugs and exceptions easily. Therefore, when handling errors in your application, you must be careful and wise.

This post will discuss Error Handling in Express.js and show you how to write and set up your own custom Error handler middleware function in Node.js/Express.js API.

Here is a list of the significant points that we covered in this post:

  1. The default Error handling in Express.js.
  2.  How to catch errors in expess.js Synchronous code.
  3.  How to catch errors in express Asynchronous Code.
  4.  Using promises in error handling.
  5.  What is the default built-in error handling in Express.js.
  6.  Using the express-async-errors library to handle errors.
  7.  How to handle errors using a Custom Error Handler middleware function.
  8.  A practical example of Custom Error Handler middleware function.

Related Articles:

1. Error handling in Express.js

The term “Error Handling in Express” describes how Express detects and handles synchronous and asynchronous errors. Express’s default error-handling middleware is super helpful for beginners to take care of unexpected, unhandled errors, so you don’t need to create your own error handler to get started with Express because it comes with a default one. So let’s take a look at how express catches and processes errors.

1.1. Express Error Catching in Synchronous Code

Code statements run sequentially and one at a time are called synchronous code. Express detects errors when they occur in synchronous code inside routes and middleware automatically and will catch and process them without requiring any extra work.


Here is an example of Express error handling in synchronous code:

app.get('/', (req, res) => {
  throw new Error('Error!!!!') // Express will catch this error automatically and will process it on its own by sending the error's status code and message to the client .
})

1.2. Express Error Catching in Asynchronous Code

Most route handlers and middleware in server code will use asynchronous Javascript logic to query databases and use external APIs. In Express versions < 5, developers must pass the errors returned from asynchronous functions invoked by these route handlers and middleware to the next() function to invoke the built-in error handler middleware, where Express will catch and process them.


Here is an example of Express error handling in Asynchronous code:

 
 app.get('/', (req, res, next) => {
  fs.readFile('/file-not-exist', (err, data) => {
    if (err) {
      next(err) // the next() function will pass the err to Express built-in error handler middleware.
    } else {
      res.send(data)
    }
  })
})

2. Express Error handling and Promises

A Promise is a JavaScript object representing the eventual success (resolve) or failure (rejected) of an asynchronous operation and returns a value. Promise blocks are considered the best practice for catching and handling errors in JavaScript since they allow for avoiding the overhead of the try…catch block and automatically catch both synchronous errors and rejected promises.

Nowadays, most libraries that provide async operations return promises. From Express 5 onwards, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error. In this case, the next() function will be called with either the thrown error or the rejected value. If no rejected value is provided, next will be called with a default Error object provided by the Express router.

If you pass anything to the next() function except the string ‘route’, Express regards the current request as being an error and will skip any remaining non-error handling routing and middleware functions.

Let’s take a look at the following examples of catching errors from promises:

Example1: using the async…await with getUserById()

In the following route handlers, the next() function is called automatically in case of rejection or throwing an error.

 
 app.get('/user/:id', async (req, res, next) => {
  const user = await getUserById(req.params.id)
  res.send(user)
})

Example2: using the readFile() of fs library

In this example, we enable Express to catch the error in the promise by providing next() as the final catch handler.

const fs= require('fs')
const fsPromises = fs.promises
app.get('/', (req, res, next) => {
  fsPromises.readFile('./no-such-file.txt')

     .then(data => res.send(data))

     .catch(err => next(err)) 

3. The default built-in error handling in Express.js

Express offers a built-in error handler that catches and processes errors that might be encountered in your application. Beginners will find this default error-handling middleware for Express to be quite helpful in handling issues for its capability of catching errors and preventing applications from crashing.
This default error-handling middleware function is added at the end of the middleware function stack.
As we said already if the functions called from your route handlers are synchronous functions, the Express built-in error handler will catch the error and responds to the client with an error status code, error message, and stack trace ( in case you are in development environment) without extra work and if these invoked functions are asynchronous functions the thrown errors need to be passed to the next() function then handled and processed differently because they may crash the application.
When you pass an error to next(), it will be handled by the built-in error handler, the error will be written to the client with the stack trace in the production environment ( NODE_ENV = ‘production’ ), and the response will include:

  • The res.statusCode is set from err.status (or err.statusCode). If this value is outside the 4xx or 5xx range, it will be set to 500.
  •  The res.statusMessage is set according to the status code.
  •  The body will be the HTML of the status code message when in a production environment; otherwise will be err.stack.
  •  Any headers specified in an err.headers object.

Giving all this extra information about the error to the user (client side) is not safe and elegant. Besides, if you call next() with an error while streaming the response to the client, the Express default error handler closes the connection and fails the request.

To improve this behaviour, you need to add custom error handling functions where you can handle all the errors passed to the next() function in the asynchronous route handlers.

4. Writing the error handler middleware function

This solution aims to place all our specified error-handling logic inside one middleware called error-handler instead of defining the handling behaviour inside each route. So this way, the error handler middleware will catch any errors thrown in any middleware and routes, and the appropriate customized response will be sent to the client or to log the errors to a file or a database.

The error handler is a particular middleware in Node.js which takes four parameters. In addition to the three parameters (req, res and next ) found in a standard route middleware, the Error handler also takes one additional parameter, the actual error. The four parameters we are talking about are:

  1. err (error).
  2.  req (request).
  3.  res (response).
  4.  next ( next() ).

4.1. Error handler using express-async-errors library

The express-async-errors library is used to catch and handle errors in your asynchronous middleware and routes in an easy and customized way. Here is an example of how you can use it:

  1. Install and Require the library at the top of your app:
 npm install express-async-errors --save
const express = require('express');
require('express-async-errors');
const app = express();
 
app.get('/', async (req, res) => {
  .......
});

2. Create the error handler middleware function:

As we said already, we define an error-handling middleware function in the same way as other middleware functions, except error-handling functions have four arguments instead of three: (err, req, res, next):

 
 function errorHandler(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something went wrong!');
}

  1. Calling the error-handler using the app.use() function

The handler needs to be lowered as far as possible in the application after all routes and other middleware and handlers, so we should specify the error handler middleware as the last middleware in the chain:

 
 app.use(errorHandler);

4.2. Example of Custom Error Handler middleware function

Creating your own middleware function to specify your error-handling logic is considered a smart choice for beginners and a necessity for advanced developers. 

The custom error handler middleware is used to catch all errors and remove the need for duplicated error handling code throughout the boilerplate application. It’s configured as middleware in the main server.js file. Always remember that the error-handler needs to be lowered as far as possible in the application after all routes and other middlewares and handlers. If we specify routes or middlewares after registration of errorHandler, then the error handler will not catch exceptions which appear in those routes or middlewares.

server.js:

 app.use(errorHandler);

In this section, we will present a custom error handler middleware function (we call it: errorHandler) that handles different types of errors differently.

Our errorHandler middleware function will check the type of the error and sends a specific response depending on the type of error as follows:

  1. All errors of type ‘string’ are treated as custom errors; this will simplify the code since only a string needs to be thrown.
  2.  If a custom error ends with the words ‘not found’ a 404 response code is returned. Otherwise, a standard 400 response is returned as a response.
  3.  If the error is an ‘UnauthorizedError’, then the error is treated as an authentication error, and 401 response is returned with the message ‘Unauthorized’.
  4.  Otherwise, a default response is sent with the code 500 with the message ‘Something went wrong!’.
  5.  Keep in mind that you can add as many checks for different error types as you need and customize the responses accordingly. You can also log the errors to a file or a database for further analysis.

The following example of a custom error-handler is configured and used in the node.js APIs we built in previous articles. So if you want to go further and see practical examples of errors handling, go ahead and check these two posts:

   function errorHandler(err, req, res, next) {
    switch (true) {
        case typeof err === 'string':
            // custom error
            const is404 = err.toLowerCase().endsWith('not found');
            const statusCode = is404 ? 404 : 400;
            return res.status(statusCode).json({ message: err });
        case err.name === 'UnauthorizedError':
            //  authentication error
            return res.status(401).json({ message: 'Unauthorized' });
        default:
            return res.status(500).send('Something went wrong!');
    }
}

5. Conclusion

Handling errors in a custom middleware function in Node.js will save you valuable time and help you to write clean and maintainable code by avoiding code duplication and missing some errors without being treated.
This post aimed to help you understand how to catch errors and how to handle them in express.js, then show you a practical way to handle errors in your express/node.js API by creating your own custom error-handler middleware function.

You might also like:

Complete JWT Authentication and Authorization System for MySQL/Node.js API.

How to Build a Complete API for User Login and Authentication using MySQL and Node.js.

Node.js + MySQL : Add Forgot/Reset Password to Login-Authentication System.

Nodemailer + Gmail: How to Send Emails from Node.js API using Gmail.

How to store Session in MySQL Database using express-mysql-session.

How to interact with MySQL database using async/await promises in node.js ?

How to use Sequelize async/await to interact with MySQL database in Node.js.

MANY-TO-MANY Association in MYSQL Database using Sequelize async/await with Node.js.

ONE-TO-ONE Association in MYSQL Database using Sequelize async/await with Node.js

ONE-TO-ONE Association in MYSQL Database using Sequelize async/await with Node.js.

How to add Routes to insert data into MySQL database-related tables in Node.js API?

Example How to use initialize() Function in Node.js/Express API .

Why is Connection Pooling better than Single Connection?.

How to create MySQL database using node.js.

How to Get Current Date and Time in JavaScript.

Leave a Comment

Your email address will not be published. Required fields are marked *

Translate »