Skip to content

Globally handle async errors on Express

Why globally handle errors in Express?

According to express documentation "Express would not catch the error since it is not part of the synchronous handler code." what this means is that if we don't put our asynchronous code inside a try...catch block, and we get an error the whole Express app is going to crash, this can lead to unexpected downtime of our project. Some developers don't want to add multiple try and catch block to all the controllers and routes, so I'm going to show you a way to catch all errors from controllers and routes without needed to add this blocks everytime you want to do something asynchronous

Getting started

First we need to create a new folder an initialize a node project

mkdir globally-handle-express-errors
cd globally-handle-express-errors
npm init -y

Installing npm modules

This is a list of all the packages needed and the reason why we will install them:

"express" (npm install express): Simplifies the creation of RESTful APIs
using Node
"express-async-errors" (npm install express-async-errors --save): A dead simple ES6 async/await support hack for ExpressJS
npm i express express-async-errors

Initializing server

Now we will create our express server, first create an app.js file on the base of the project, this file will contain the initialization of our express app, some routes that will trigger asynchronous errors and our global error handler middleware

// ./app.js

const express = require("express");

// Import custom error class
const { CustomError } = require("./error");

// Initialize express app
const app = express();

// Converts incoming JSON data to objects
app.use(express.json());

// Basic route to test that the server is working
app.get("/health", (req, res) => {
    res.status(200).send('Working');
})

// Returns an error inside a promise
const promiseWithError = () => {
    return new Promise((res) => {
        throw new Error("Error found")
    });
}

// Returns a custom error inside a promise
const promiseWithCustomError = () => {
    return new Promise((res) => {
        throw new CustomError("Error found", "Custom data")
    });
}

// Route without try and catch encountering an error
app.get("/promise-with-error", async (req, res) => {

    await promiseWithError();

    res.status(200).send('Success');
})

// Controller for promise-with-error route
const promiseWithCustomErrorController = async (req, res) => {
    await promiseWithCustomError()

    res.status(200).send('Success');
}

// Route without try and catch encountering a custom error, using controller
app.get("/promise-with-error", promiseWithCustomErrorController)


/**
 * Use express-async-errors middleware to catch all errors even-thought code is not surrounded in try...catch block,
 * use after adding routes or load them if using a loader, it works also with Typescript
 */
app.use((err, req, res, next) => {

    // Default error message
    let errorMessage = "Internal server error please contact support";

    // We can check if error is instance of a specific error type and return a specific message or data
    if (err instanceof CustomError) {
        errorMessage = `Custom error: ${err.message}, custom data: ${err.customData} )`;
    }

    // Check that headers were not already sent, to avoid "Can't set headers after they are sent to the client" error
    if (!res.headersSent) {
        res.status(500).json({ msg: errorMessage });
    }

    next();
});

module.exports = app;

now create a new file called error.js on the base on the project. Here we will create a custom Error class to show you how we can throw and handle custom errors, returning different data depending on the error type

// ./error.js

// Custom error class with custom data
class CustomError extends Error{
    constructor(message, customData) {
        // Initialize parent Error class with message received from the constructor
        super(message);

        // Example string showing we can add custom information to our custom error type, we can add multiple fields and different types of data
        this.customData = customData;
    }
}

module.exports = {
    CustomError
}

finally create a new file called index.js on the base of the project. We will use the app from above to listen for new requests

// ./index.js

// Don't forget to import express-async-errors before you start using express or declare your express app
require('express-async-errors');

// Import app from "./app file
const app = require("./app");

// Port that will be uses to listen to requests
const port = process.env.PORT || 3015;

/**
 * Listen on port 3015
 */
app.listen(port, () => {
    console.log(`App listening on port ${port}!`);
});

Testing

Start your app with "node index.js" and go to your browser and search for http://localhost:3015/promise-with-error and http://localhost:3015/promise-with-custom-error you will se the following messages

{ "msg": "Internal server error please contact support" }
{ "msg": "Custom error: Error found, custom data: Custom data )" }

Important

Don't forget to add "require('express-async-errors');" before you import or initialize your express app and don't forget to add the middleware that will handle the errors using "app.use((err, req, res, next) => {})"

GitHub repository

In this repository https://github.com/obravocedillo/GloballyHandleExpressErrors you can find the code.