A Quick Guide to Handling Express.js Errors in Your Application

Written by dhruv479 | Published 2021/03/17
Tech Story Tags: expressjs | nodejs | web-development | software-development | backend-developer | javascript | programming | coding | web-monetization

TLDRvia the TL;DR App

Handling Errors is overlooked time and again by inexperienced developers, but this has a significant impact on the stability, monitoring, and performance of the application. In this article, I will explain how to handle errors efficiently in your express application.
There is no definite way to handle errors, but some are better than others. let’s dive into this.

Handle Errors in Controller (Bad Approach):

In this approach, we handle errors in the controllers. For every failed condition/exception, the error will be raised and returned by the controller itself in the response.
For example:
const express = require('express');
const bodyParser = require('body-parser');

const app = express();
const port = 3000;

app.use(bodyParser.json());

app.post('/post', async (req, res) => {
  const { title, author } = req.body;

  if (!title || !author) {
    return res.status(400).json({
      status: 'error',
      message: 'Missing required fields: title or author'
    });
  }

  try {
    const post = await db.post.insert({ title, author });
    res.json(post);
  } catch (error) {
    return res.status(500).json({
      status: 'error',
      message: 'Internal Server Error'
    });
  }
});

app.listen(port, () =>
  console.log(`app is listening at http://localhost:${port}`)
);
In this, we have a route /post that is adding a new Post in the database. The errors are handled explicitly at every condition and return a response.

This kind of design has multiple caveats that can increase the system complexity. For example, if you want to track all the errors thrown by the system, you have to invoke that piece of code(tracker) everywhere, even
multiple times in a single controller. 😑 Now that’s something, you had never want to do!
Note: This type of application architecture will hinder the scaling of the app and also it will be really hard to track the errors altogether.

How to Handle Errors in Your Application (Better Approach):

In this approach, we use a common error handler for the complete app. Yes! all the errors are handled in the same place. To make the process more smooth, we will use
@hapi/boom
npm library to generate errors with respective codes.
const express = require('express');
const bodyParser = require('body-parser');
const Boom = require('@hapi/boom');

const app = express();
const port = 3000;

app.use(bodyParser.json());

app.post('/post', async (req, res, next) => {
  const { title, author } = req.body;
  try {
    if (!title || !author) {
      throw Boom.badRequest()
    }
    const post = await db.post.insert({ title, author });
    res.json(post);
  } catch (error) {
     next(error);
  }
});

// error handler
app.use((error, req, res, _) => {
  const { message = 'Oops! Something went wrong', isBoom, output } = error;
  
  if (isBoom) {  // if the error is explicitly thrown
    return res.status(output.statusCode).json({
      message,
      success: false,
    });
  }

  // return generic error response for unexpected error
  return res.status(500).json({
    success: false,
    message: 'Oops! Something went wrong',
  });
});

app.listen(port, () =>
  console.log(`app is listening at http://localhost:${port}`)
);
This might seem a bit complicated initially but it is way better than the preceding approach.
In Express, we get
next
in every Controller as the third parameter and we simply use that to pass any error generated to our error handler.
This way, all the errors will pass through this handler and you can easily
track the errors generated and take actions to counter system failure.

Also, this design will help you scale your application, you won’t need to
return an error for every condition but simply pass any error raised to the
error handler using next and the rest can be taken care of there.
You can also integrate error monitoring tools like
Sentry
in the system to keep track of the critical errors that can lead to a system halt.

Conclusion:

The basic principle of designing systems should be to keep things as simple as possible which supports scaling too for the bigger picture.
Checkout the boilerplate that I have created for Express, Mongoose. You are going to like it: https://github.com/dhruv/node
I hope you have enjoyed this article and found it valuable. You can follow me on Twitter. Thanks for the support!
Also published on: https://javascript.plainenglish.io/how-to-handle-errors-in-express-js-9fe2522ccc53

Written by dhruv479 | I’m a Software Engineer from India. In ♡ with JavaScript and all amazing things there. Reach me: hey@dhruv479.dev
Published by HackerNoon on 2021/03/17