In the last post, we did some basic setup for our express app to respond to GET requests at / with an HTML response that included "Hello world." Here's what we had in app.js at the end of that post:

const express = require('express');  
const app = express();

app.get('/', (req, res) => res.send(`  
<html>  
  <head></head>
  <body>
    <h1>Hello world!</h1>
  </body>
</html>  
`));

app.listen(3000, () => console.log('Express listening on port 3000'));  

In this post, we're going to add some error handling. Because no matter how hard you try, your users will find a way to break your code. Let's start by adding a route that throws an error:

const express = require('express');  
const app = express();

app.get('/', (req, res) => res.send(`  
<html>  
  <head></head>
  <body>
    <h1>Hello world!</h1>
  </body>
</html>  
`));

// This will throw
app.get('/err', (req, res) => req.foo.bar.not.there = true);

app.listen(3000, () => console.log('Express listening on port 3000'));  

If you start up your app with npm start and open your browser to localhost:3000/err, you should see a stack trace. Express handled the error for us (the app didn't crash, although you notice that it did spew the same stack trace in your console), but it's not a great user experience to be seeing the stack trace from an error, and it could be a security concern, as we're exposing things about the internals of our app that an attacker could potentially use against us. Let's instead handle this error ourselves a little more gracefully.

In express, an error handler is similar to a regular route handler, except that you don't supply a path (and you usually wouldn't want to anyway, since an error handler is often global . . . that is, you usually want to render the same error page, regardless of where the error occurred), and the handler signature has an additional parameter: err. Instead of app.get, we'll use the more generic app.use which is a function for providing common "middleware" to run before or after other routes. We'll talk more about middleware in other contexts in later posts, but for now what you need to know is that middleware handlers are functions shared by multiple routes. Let's add one to our code, as that will make it easier to understand what I'm saying:

const express = require('express');  
const app = express();

app.get('/', (req, res) => res.send(`  
<html>  
  <head></head>
  <body>
    <h1>Hello world!</h1>
  </body>
</html>  
`));

// This will throw
app.get('/err', (req, res) => req.foo.bar.not.there = true);

app.use((err, req, res, next) => res.send(`  
<html>  
  <head></head>
  <body>
    <h1>Uh oh...</h1>
    <h3>Something went wrong</h3>
  </body>
</html>  
`));

app.listen(3000, () => console.log('Express listening on port 3000'));  

Here we're telling express that we want to use this particular middleware function (specifically an error handler) for all routes that reach this part of the code. Express evaluates routes and handlers for each request in the order they're added, so in the case of the GET / route handler, since we handle the route and send a response, the execution path never reaches this middleware handler. In the GET /err handler, however, an error is thrown which kicks execution out of the route handler, where it proceeds to the next matching route handler or middleware. And specifically, when an error is thrown from a route handler, the next error handler middleware will be invoked with the error as the first argument. We're not using that error here, and probably most of the time you don't want to, at least not in your response, as again that's exposing something about the internals of your app, but you might want to log it, so you can later investigate why it happened (and at the very least, know that it did happen).

So now we have a little bit of security knowing that our app isn't going to blow up at the first error. It's important to know that the error handling provided by express will only catch errors in synchronous code. Errors thrown from asynchronous code will not be caught, but there are other ways to catch those which we'll cover at another time as they'd be very out of context in this post.

Let's add one more thing to our app before we wrap up. We've handled errors of the 500 variety (internal server error), but it's still possible for a user to navigate to the wrong url and get a page that says "Cannot GET /foo." We need a catch-all route to return a 404 ("Not Found") error. To do this, we'll use the same app.use to provide a common end point for code, but we'll pass a normal route handler instead of an error handler, and we'll make the assumption that if we reach it, the request wasn't handled by another route.

const express = require('express');  
const app = express();

// Handle GET /
app.get('/', (req, res) => res.send(`  
<html>  
  <head></head>
  <body>
    <h1>Hello world!</h1>
  </body>
</html>  
`));

// This will throw
app.get('/err', (req, res) => req.foo.bar.not.there = true);

// Handle unhandled requests
app.use((req, res, next) => res.send(`  
<html>  
  <head></head>
  <body>
    <h1>Sorry, we couldn't find that page</h1>
  </body>
</html>  
`));

// Handle errors
app.use((err, req, res, next) => res.send(`  
<html>  
  <head></head>
  <body>
    <h1>Uh oh...</h1>
    <h3>Something went wrong</h3>
  </body>
</html>  
`));

app.listen(3000, () => console.log('Express listening on port 3000'));  

Now if you return to your browser and visit localhost:3000/foo or any other route we haven't explicitly defined in our code, you should see "Sorry, we couldn't find that page," which is a little nicer for users than "Cannot GET /foo."

Let's stop here and pick it up another time. We've learned how to handle (synchronous) errors and return custom HTML to tell the user that something went wrong, as well as how to handle missing routes gracefully. Next time, we'll look at better ways to render and return HTML, since it's a little gross and repetitive having ad hoc HTML right in our route handler.