How to handle errors in Express and Node.js App

mistake-3085712_640.jpg Image by mohamed Hassan from Pixabay

When we create api's with express, we define some routes and their handlers, in an ideal world, consumers of our api will only make requests to routes that we defined and our routes will work without error. But if you have noticed, we do not live in an ideal world :). Express knows this and it makes handling errors in our api's a breeze.

In this post I will explain how to handle errors in express, to follow along please clone the repository here.

Remember to npm install after cloning the repo.

The repository has a single javascript file, index.js with the following contents:

const express = require("express");

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

app.get("/", (req, res, next) => {
  res.send("Welcome to main route!");
});

app.get("/about", (req, res, next) => {
  res.send("This is the about route!");
});

app.listen(port, () => console.log(`App listening on port: ${port}`));

If you don't want to clone the repo, create a new folder, npm init -y and then npm i --save express. Create index.js in this folder and paste the code up in it.

Sources of error:

There are two basic ways an error can occur in Express app.

One way is when a request is made to a path that has no route handler defined for it. For example, index.js defines two get routes (one to / and to /about), I am using get routes so that we can easily test the routes in a browser. Note that a route defines a path and a middleware function to be called when a request is made to that path:

app.HTTPMethod(path, middleware)
// HTTPMethod = get, post, put, delete ...

Another source of error is when something goes wrong in our route handler or anywhere else in our code. For example, update the first route in index.js as follows:

...
app.get('/', (req, res, next) => {
    // mimic an error by throwing an error to break the app!
    throw new Error('Something went wrong');
    res.send('Welcome to main route!')
})
...

Restart the server and visit localhost:3000, you will be greeted with an error and a stacktrace.

Handling routing errors by ordering of routes

Remove the statement that throws the error in index.js. Start the server and visit localhost:3000 in a browser, you should see the message:

Welcome to main route!

Visiting localhost:3000/about:

This is the about route!

How does Express look up routes?

Express creates a what can be called a routing table, where it puts the routes defined in the order in which they were defined. When a request comes into the web server, the URI is run through the routing table and the first match in the table is used - even if there are more than one match.

If no match is found, then Express displays an error. To see this in action, visit localhost:3000/contact, the browser displays:

Cannot GET /contact

After checking the routing table, Express found no match for /contact, so it responds with an error.

How to exploit the order of routes

Since Express displays the error message when no match is found for a given URI in the routing table, this means that we define a route to handle errors by making sure that this route is the last on the routing table. Which path should the error route match?

Because we don't know the inexistent path the user will make a request to, we cannot hardcode a path into this error route. We also do not know which HTTP method the request might use, we will therefore use app.use() instead of app.get.

Update index.js by putting this route at the end of the route declaration, before app.listen():

...
// this matches all routes and all methods
app.use((req, res, next) => {
    res.status(404).send({
        status: 404,
        error: 'Not found'
    })
})

app.listen(port ...

Restart the server and visit a path that is not defined, e.g localhost:3000/blog

Now, we have a custom error response:

{"status":404,"error":"Not found"}

Remember that the order of the routes is very important for this to work. If this error handling route is at the top of the route declaration then every path - valid and invalid will be matched to it. We do not want this, so the error handling route must be defined last.

Handling any type of error

The solution from the previous section works if we only want to handle errors due to request to inexistent paths. But it doesn't handle other errors that may happen in our app and it's an incomplete way to handle errors. It only solves half of the problem.

Update index.js to throw an error in the first get route:

...
app.get('/', (req, res, next) => {
    throw new Error('Something went wrong!');
    res.send('Welcome to main route!')
})
...

If you visit localhost:3000 you will still see the response by Express default error handler.-

Defining an Error Handling Middleware

Error handling middeware functions are delared in the same way as other middleware functions, except that they have four arguments instead of three. For example:

// error handler middleware
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('Something Broke!');
})

Put this code after the route declaration in index.js before app.listen and after the first app.use, restart the server and then visit localhost:3000, now the response is:

Something Broke!

Now, we are handling both types of errors. Aha!

This works but can we improve it?. Yes. How?

When you pass an argument to next(), Express will assume that this was an error and it will skip all other routes and send whatever was passed to next() to the error handling middleware that was defined.

Update index.js:

...
app.use((req, res, next) => {
  const error = new Error("Not found");
  error.status = 404;
  next(error);
});

// error handler middleware
app.use((error, req, res, next) => {
    res.status(error.status || 500).send({
      error: {
        status: error.status || 500,
        message: error.message || 'Internal Server Error',
      },
    });
  });
...

The middleware function that handles bad request now hands over to the error handler middleware. next(error) implies: 'Hey, Sir error handler, I've got an error, handle it!'.

To make sure you are on the same page with me, the line error.status || 500 implies that if the error object does not have a status property, we use 500 as the status code.

If you are serving static pages intead of sending JSON response, the logic is still the same, you just have to change what happens in the error handler. For example:

app.use((error, req, res, next) => {
    console.error(error); // log an error
    res.render('errorPage') // Renders an error page to user!
  });

If you find this article helpful kindly share with your friends and followers and checkout my other posts.

You can follow me on twitter solathecoder .

Happy Hacking!