How to Handle Errors in an Express and Node.js App

Olusola Samuel
Level Up Coding
Published in
5 min readJan 13, 2020

--

Image by mohamed Hassan from pixabay

When we create APIs with Express, we define routes and their handlers. In an ideal world, the 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 makes handling errors in our API a breeze.

In this post, I will explain how to handle errors in Express. To follow along please clone this repository. Remember to npm install after cloning the repo.

The repository has a single JavaScript file, index.js with the following content:

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 errors

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 errors 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 the main route!

Visiting localhost:3000/about:

This is the about route!

How does Express look up routes?

Express creates what can be called a routing table, where it puts the routes in the order in which they were defined in the code. 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 is 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.

Centralized Error Handling: 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 nonexistent 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 the following route at the end of the route declaration, before app.listen():


// this matches all routes and all methods i.e a centralized error handler
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 errors in route handlers

As stated earlier, route handlers are just JavaScript functions ( they could also be class methods if we are writing OOP), and just like any other function, we can use try catch state to handle errors.

The try catch statement marks a block of statements to try (in the try block) and then specifies a response (in the catch block) should an Exception be thrown. An Exception in JavaScript is syntactically synonymous to an Error.

Update index.js by updating the middleware functions:

If you are performing any asynchronous operation in your middle function,

Handling any type of error

The solution from the previous section works if we only want to handle errors from requests to nonexistent 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 middleware functions are declared in the same way as other middleware functions, except that they have four arguments instead of three. For example:

// error handler middleware
app.use((error, req, res, next) => {
console.error(error.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, and 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 the 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. The complete content of index.js is:

If you are serving static pages instead 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 check out my other posts.

You can follow me on twitter @solathecoder

Happy Hacking!

What do you think will happen if you clicked on the clap icon 50 times? Try it out!

--

--

Software Engineer (Node/Typescript, Go) and Student of Computer Science at the University of Lagos.