How to Handle Errors in an Express and Node.js App
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!