{ The Rithm Blog. }

Error Handling in Express November 05, 2018

Node Logo
Express Logo

Express is a flexible framework for building server-side JavaScript web servers using Node. We teach it here at Rithm School, and students often enjoy getting a chance to use their JavaScript skills on both the front-end and back-end, using Express to build API servers.

One area that can be confusing for new users is how to handle errors well using Express. Express is so flexible that it's not clear how to make a simple, general purpose strategy for handling different kinds of errors.

To help with this, here is a simple, new-learner-friendly system for making and handling errors in Express.

Rithm Students in Lecture

Common Types of Errors

Out of the box, recent versions of Express will return reasonable error pages for both 404 Not Found errors, and well as 500 Server Error errors. However, these built-in responses are HTML responses, which is not typically what you'd want to provide for a server that focuses on returning JSON APIs.

We want to make sure our server returns JSON error messages for these kinds of cases:

  • if a route cannot be found, it should return a 404 status response, along with a JSON message explaining the error.

  • if a route throws an exception (either accidentally, because of a bug in the code, or intentionally, because an error is throw), the server should return a 500 error with information in a JSON response.

  • for specialized cases, we want to make it easy for the developer to raise specific errors and ensure they have the right HTTP status code. For example, if a username/password isn't correct, you'd want to return a 401 error code with a JSON response explaining an authorization problem.

Our errors.js File

We put information about error-handling in one file, errors.js:

/** Make error. Makes error from message or throws passed-in err  */

function makeError(message, status) {
  let err = message instanceof Error ? message : new Error(message);
  err.status = status;
  return err;
}

/** handler for 404 routes. */

function error404(req, res, next) {
  let err = makeError('Not Found', 404);
  // pass the error to the next piece of middleware
  return next(err);
}

/** general error handler */

function handleRouteErrors(error, req, res, next) {
  // for actual JS exceptions, log the exception stack
  if (error.stack) console.error(error.stack);

  res.status(error.status || 500).json({ error: error.message });
}

module.exports = {
  makeError,
  error404,
  handleRouteErrors
};

This file has three parts:

  • a useful makeError function. This create a real JS Error from a passed-in message and assigns an HTTP status to the error. By having a single function to do this, it makes it less laborious when we need to create errors throughout our code.

  • a 404 handler that will help generate a nice-looking JSON 404 response.

  • a general error handler that will print a traceback to the console, and respond with a JSON error message.

Using Our Error Handlers

Here's a simple, sample Express app that uses this error handling:

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

const { makeError, error404, handleRouteErrors } = require('./errors.js');

const people = [
  { id: 'elie', name: 'Elie' },
  { id: 'joelburton', name: 'Joel' }
];

/* GET /person : returns {"people": [personData, personData]} */

app.get('/person', function(req, res, next) {
  return res.json({ people });
});

/* GET /person/[id] : returns {"person": personData]} or 404 if not found */

app.get('/person/:id', function(req, res, next) {
  const id = req.params.id;
  const person = people.find(p => p.id === id);
  if (!person) {
    next(makeError('No such person', 404));
  }
  return res.json({ person });
});

/* GET /example-error : a route with a bug in it */

app.get('/example-error', function(req, res, next) {
  some_undefined_function();
});

/* GET /example-api : a route with an AJAX request that fails. */

app.get('/example-api', async function(req, res, next) {
  try {
    const apiRes = await axios.get('http://joelburton.com/no-such-page');

    // we will never get here ...
    return res.json({ info: apiRes.data.info });
  } catch (err) {
    return next(err);
  }
});

/* provide 404 response if no routes match */
app.use(error404);

/* general error-handler: returns JSON with error info */
app.use(handleRouteErrors);

app.listen(3000, function() {
  console.log('listening on 3000');
});

It demonstrates the following:

  • It doesn't get in the way of a successful request, like one to GET /person.

  • For GET /person/elie, this also works---that route raises no error, so it will return the proper JSON response.

  • For GET /person/no-such-person, that route can intentionally throw a 404 error, since that user can't be found.

  • For GET /example-error, we have a bug in our code (intentional, here, for demonstration purposes, but this could be a real bug!). JS throws an exception when it tries to call some_undefined_function, which is caught and shown as a 500 error with a JSON message.

  • For GET /example-api, our route tries to make a HTTP request (using the excellent axios library), but it gets a 404, since the requested page does not exist).

Since this is an async function, we need to wrap it in a try/catch --- this is required by Express, so that it can handle the error properly. In our error-catching part, we call next(err), to pass along the error to our general error handler.

At the bottom of those routes is our 404 error route. By having this below all good routes, any request for other resources will end up here --- which raises a 404 response, providing a nice JSON response.

Below that is our general error handler. This catches errors (a route that takes 4 arguments handles errors in Express). This sets the proper HTTP status code (default to 500 if none was given) and sends a JSON response with information about the error.

Rithm Students in Lab

Security

This code is friendly and suitable for development work or sites that aren't public facing.

As written, it does provide information about the cause of errors in the JSON response, which might leak things like variable names or function names, making this a possible security hole.

This is helpful when testing your site, or when learning Express, but, for a public site, you should modify our handleRouteErrors so that it doesn't show the error message for 500 errors. That way, the errors that you create by hand with other status codes will show up with full details; for 500 errors, you could replace the message with something suitably generic, like "Internal Server Error".

Conclusion

Making a function that can make errors and keep track of the desired status code can make it simpler to create and throw errors in your code, and having a nice JSON-friendly 404 route and a JSON-producing error handler can make your API server provide better responses that HTML pages.

I hope this is useful for those of you learning Express!

Written by Joel Joel

Back to all posts

Get Started with Rithm School