Anatomy of Error Handling Middleware in Express.js

By Hemanta Sundaray on 2022-11-17

Whenever an error is thrown somewhere in our code, we want to be able to communicate that there was a problem to the user.

In Express, error handling middleware needs to be the last app.use() in your file. If an error happens in any of our routes, we want to make sure it gets passed to our error handler. The middleware stack progresses through routes as they are presented in a file; therefore the error handler sits at the bottom of the file.

Error handling middleware

An error handler is written much like other kinds of middleware. The biggest difference is that there is an additional parameter in our callback function, err. This represents the error object, and we can use it to investigate the error and perform different tasks depending on what kind of error was thrown.

To invoke an error handler, we pass an error object as an argument to next(). Usually, next() is called without arguments and will proceed through the middleware stack as expected. However, when called with an error as the first argument, it will call the error-handling middleware.

Consider the example below:

const express = require("express")
const morgan = require("morgan")

const app = express()

app.use(morgan(":method :url :status :response-time ms"))

app.get("/users/:id", (req, res, next) => {
  const err = new Error("User not found")
  err.status = 404
  next(err)
})

app.use((err, req, res, next) => {
  const status = err.status || 500
  res.status(status).send(err.message)
})

const PORT = process.env.PORT || 5000

app.listen(PORT, () => {
  console.log(`Server listening on port ${PORT}`)
})

As we can see, in the route handler function, we are creating an error object using the Error() constructor and passing it to next(). This will trigger the error-handling middleware that we have defined at the bottom of the file.

Note: Error() can be called with or without new. Both create a new Error instance.

Now, if we visit the URL http://localhost:5000/users/1, we will get the error message:

Error Message

http-errors

To create HTTP errors, we can also use a Node.js module called http-errors.

First install http-errors:

npm install http-errors

Then require http-errors at the top of the file.

Then, we can create an error object using the createError() function. We pass the status code as a number as the first argument and the message of the error as the second argument to the createError() function.

Let’s rewrite our example using the http-errors module.

const express = require("express")
const morgan = require("morgan")
const createError = require("http-errors")

const app = express()

app.use(morgan(":method :url :status :response-time ms"))

app.get("/users/:id", (req, res, next) => {
  next(createError(404, "User not found"))
})

app.use((err, req, res, next) => {
  const status = err.status || 500
  res.status(status).send(err.message)
})

const PORT = process.env.PORT || 5000

app.listen(PORT, () => {
  console.log(`Server listening on port ${PORT}`)
})

Join the Newsletter