Master coding by building complete, real-world software projects on DevCoach!

Handle errors using the error middleware in Express.js

In an Express server, errors can be handled in multiple ways. One way is to return a response directly from the function where the errors occur. However, doing so results in unmaintainable and redundant code, as it requires writing the same logic repeatedly.

An alternative and recommended solution to this problem is to use middleware. By using middleware, we can encapsulate all the error-handling logic within a single function, which significantly improves maintainability and reduces redundancy in our application.

The first step to creating and using an error middleware in an Express app is to install the express-async-errors package as a dependency in the project. This package helps handle errors thrown from asynchronous functions. Once the package is installed, add the following line in your app.ts file:

import 'express-async-errors';

Make sure to import the package at the top of the file to capture errors in all the modules imported below it. That's it; we don't need to do anything else with this package.

Error middleware

Now, let's create the error middleware with the following code:

import { ErrorRequestHandler } from 'express';

const handler: ErrorRequestHandler = (err, req, res) => {
  // JWT errors
  if (err.name === 'JsonWebTokenError' || err.name === 'TokenExpiredError') {
    return res.status(401).json({ message: 'Invalid credentials' });
  }

  // Custom error
  res.status(res.statusCode || 500).json({
    message: err.message,
  });
};

export default handler;

In the above code, we export a handler function. The function accepts three parameters: err, req, and res, which correspond to the error, request, and response objects.

Within the function, we check for two types of errors: errors thrown by third-party packages and custom errors thrown by developers. To identify and handle errors thrown by third-party packages, we use error properties such as name and return responses corresponding to the errors.

For example, using the jsonwebtoken package in an Express app will throw an error named JsonWebTokenError when it fails to parse a token and TokenExpiredError when a token is expired. In the middleware, we check for these errors and return an 'Invalid credentials' response.

The other type of errors are custom errors thrown by developers using throw new Error(). In the middleware, we check for these errors at the end. So, if all the third-party error checks fail, we assume that it's a custom error and return a response with the error message and the statusCode set by the developer.

This is how we check for and handle all possible errors that may occur throughout the application using the middleware. Now, to use the middleware, navigate back to the app.ts file, import the middleware, and place it below all the other middleware, as in the following code:

import error from './middleware/error';
import 'express-async-errors';
import User from './routes/user';

const PORT = process.env.PORT || 5100;

const app = express();

// Middleware
app.use(express.json());
app.use('/users', User);
app.use(error);

app.listen(PORT, () => console.log('Server started'));

In the above code, we've imported the error middleware and placed it at the bottom of the other middleware, just above the app.listen method. By doing so, we ensure that errors thrown from anywhere in the app will be passed to the error middleware.

Throwing error

Let's now take a look at a typical Express route where we deal with both custom errors and errors thrown by third-party packages:

router.post('/', async (req, res) => {
  if (!req.body.email) {
    res.status(400);
    throw new Error('Email is required');
  }

  try {
    const decoded = jwt.verify(
      token,
      process.env.JWT_SECRET as string
    ) as JwtPayload;
    // Do something with the decoded data
    res.status(200).json({ message: 'Success' });
  } catch (err) {
    console.log(err);
    throw err;
  }
});

In the above Express route, first, we throw a custom error if no email address is provided in the request body. Next, in the try block, we try to verify a JWT token, which can fail and throw errors. In the catch block, we catch the JWT errors and throw them again. We throw both the custom errors and the errors thrown by the jsonwebtoken package from the route and handle them in the error middleware.

That's it! You can now throw errors from anywhere in your application, identify them in the middleware, and return appropriate responses. Hope you found this article useful and now know why and how we use error middleware in an Express app. Happy learning!

Share this article with your friends

Copy URL

Elevate your JavaScript and solopreneur journey

Supercharge your JavaScript skills and solopreneur career. Subscribe now for expert tips and insights!

We use cookies to personalize your site experience.