DEV Community

Cover image for A simple way to handle errors in Express.js
Anderson. J
Anderson. J

Posted on

A simple way to handle errors in Express.js

Exists many ways to handle Errors in Express. A common way to do it it's using the default express middleware.

app.use(function(err, req, res, next) {
    console.error(err.stack);
    res.status(500).send('Something broke!');
}
Enter fullscreen mode Exit fullscreen mode

Another way could be to handle errors inside of controllers.

router.get("/users", async (req, res) => {
    try {
        const users = await User.getAll();
    }
    catch(err) {
        return res.status(500).json({
            message: "Something broke!",
        });
    }

    res.status(200).json({
        message: "success",
        data: users,
    });
})
Enter fullscreen mode Exit fullscreen mode

That handling way could cause some problems to keep the code clean. This problem becomes more evident when we have many controllers or some functions like the next example:

router.get("/profile", async (req, res) => {
    try {
        const {username} = await User.auth();
        const preferences = await User.getPreferences(username);
    }
    catch(err) {
        if(err instance of AuthError) {
            return res.status(401).json({
                message: "Unauthorized",
            });
        }
        else {
            return res.status(500).json({
                message: "Something Broke!",
            });
        }
    }

    res.status(200).json({
        message: "success",
        data: users,
    });
});

Enter fullscreen mode Exit fullscreen mode

We can see how the code becomes less readable because of try/catch blocks. If we have other routes where we need to call similar functions we will be repeating code, and that's not ideal.

A simple solution.

A simple solution for this problem is using a wrapper that contains the controller function and letting the wrapper handle the exception.
The wrapper looks like this:

const errorHandlerWrapper = (promiseCallBack) => {
  return async (req, res, next) => {
    try {
      await promiseCallBack(req, res, next);
    } catch (err) {
      if (err instanceof AuthError) {
        res.status(401).json({
          message: "Unauthorized",
        });
      } 
      else {
        console.log(err);
        res.status(500).json({
          message: "Something Broke!",
        });
      }
    }
  };
};
Enter fullscreen mode Exit fullscreen mode

errorHandlerWrapper receives a controller used as a callback. Then, it returns a function that executes the callback inside a try/catch block.

In that way, we delegate error handling outside of controllers.

After refactoring the code, it looks like this:

router.get("/profile", errorHandlerWrapper(async (req, res) => {
    const {username} = await User.auth();
    const preferences = await User.getPreferences(username);

    res.status(200).json({
        message: "success",
        data: users,
    });
}));
Enter fullscreen mode Exit fullscreen mode

Note how the code reduced and became more readable. All this while we are still effectively handling errors.

Conclusion

It's important to keep route controllers readable. When the code begins to grow, keeping controllers clean could become a challenge. Implementing an alternative to repetitive code blocks it's the first step to face it.

If you know alternative ways to handle exceptions within controllers, let me know in the comments ✌

Top comments (0)