Express create error

Error Handling refers to how Express catches and processes errors that occur both synchronously and asynchronously. Express comes with a default error handler so you don’t need to write your own to get started.

Error Handling refers to how Express catches and processes errors that
occur both synchronously and asynchronously. Express comes with a default error
handler so you don’t need to write your own to get started.

Catching Errors

It’s important to ensure that Express catches all errors that occur while
running route handlers and middleware.

Errors that occur in synchronous code inside route handlers and middleware
require no extra work. If synchronous code throws an error, then Express will
catch and process it. For example:

app.get('/', (req, res) => {
  throw new Error('BROKEN') // Express will catch this on its own.
})

For errors returned from asynchronous functions invoked by route handlers
and middleware, you must pass them to the next() function, where Express will
catch and process them. For example:

app.get('/', (req, res, next) => {
  fs.readFile('/file-does-not-exist', (err, data) => {
    if (err) {
      next(err) // Pass errors to Express.
    } else {
      res.send(data)
    }
  })
})

Starting with Express 5, route handlers and middleware that return a Promise
will call next(value) automatically when they reject or throw an error.
For example:

app.get('/user/:id', async (req, res, next) => {
  const user = await getUserById(req.params.id)
  res.send(user)
})

If getUserById throws an error or rejects, next will be called with either
the thrown error or the rejected value. If no rejected value is provided, next
will be called with a default Error object provided by the Express router.

If you pass anything to the next() function (except the string 'route'),
Express regards the current request as being an error and will skip any
remaining non-error handling routing and middleware functions.

If the callback in a sequence provides no data, only errors, you can simplify
this code as follows:

app.get('/', [
  function (req, res, next) {
    fs.writeFile('/inaccessible-path', 'data', next)
  },
  function (req, res) {
    res.send('OK')
  }
])

In the above example next is provided as the callback for fs.writeFile,
which is called with or without errors. If there is no error the second
handler is executed, otherwise Express catches and processes the error.

You must catch errors that occur in asynchronous code invoked by route handlers or
middleware and pass them to Express for processing. For example:

app.get('/', (req, res, next) => {
  setTimeout(() => {
    try {
      throw new Error('BROKEN')
    } catch (err) {
      next(err)
    }
  }, 100)
})

The above example uses a try...catch block to catch errors in the
asynchronous code and pass them to Express. If the try...catch
block were omitted, Express would not catch the error since it is not part of the synchronous
handler code.

Use promises to avoid the overhead of the try...catch block or when using functions
that return promises. For example:

app.get('/', (req, res, next) => {
  Promise.resolve().then(() => {
    throw new Error('BROKEN')
  }).catch(next) // Errors will be passed to Express.
})

Since promises automatically catch both synchronous errors and rejected promises,
you can simply provide next as the final catch handler and Express will catch errors,
because the catch handler is given the error as the first argument.

You could also use a chain of handlers to rely on synchronous error
catching, by reducing the asynchronous code to something trivial. For example:

app.get('/', [
  function (req, res, next) {
    fs.readFile('/maybe-valid-file', 'utf-8', (err, data) => {
      res.locals.data = data
      next(err)
    })
  },
  function (req, res) {
    res.locals.data = res.locals.data.split(',')[1]
    res.send(res.locals.data)
  }
])

The above example has a couple of trivial statements from the readFile
call. If readFile causes an error, then it passes the error to Express, otherwise you
quickly return to the world of synchronous error handling in the next handler
in the chain. Then, the example above tries to process the data. If this fails then the
synchronous error handler will catch it. If you had done this processing inside
the readFile callback then the application might exit and the Express error
handlers would not run.

Whichever method you use, if you want Express error handlers to be called in and the
application to survive, you must ensure that Express receives the error.

The default error handler

Express comes with a built-in error handler that takes care of any errors that might be encountered in the app. This default error-handling middleware function is added at the end of the middleware function stack.

If you pass an error to next() and you do not handle it in a custom error
handler, it will be handled by the built-in error handler; the error will be
written to the client with the stack trace. The stack trace is not included
in the production environment.

Set the environment variable NODE_ENV to production, to run the app in production mode.

When an error is written, the following information is added to the
response:

  • The res.statusCode is set from err.status (or err.statusCode). If
    this value is outside the 4xx or 5xx range, it will be set to 500.
  • The res.statusMessage is set according to the status code.
  • The body will be the HTML of the status code message when in production
    environment, otherwise will be err.stack.
  • Any headers specified in an err.headers object.

If you call next() with an error after you have started writing the
response (for example, if you encounter an error while streaming the
response to the client) the Express default error handler closes the
connection and fails the request.

So when you add a custom error handler, you must delegate to
the default Express error handler, when the headers
have already been sent to the client:

function errorHandler (err, req, res, next) {
  if (res.headersSent) {
    return next(err)
  }
  res.status(500)
  res.render('error', { error: err })
}

Note that the default error handler can get triggered if you call next() with an error
in your code more than once, even if custom error handling middleware is in place.

Writing error handlers

Define error-handling middleware functions in the same way as other middleware functions,
except error-handling functions have four arguments instead of three:
(err, req, res, next). For example:

app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

You define error-handling middleware last, after other app.use() and routes calls; for example:

const bodyParser = require('body-parser')
const methodOverride = require('method-override')

app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use((err, req, res, next) => {
  // logic
})

Responses from within a middleware function can be in any format, such as an HTML error page, a simple message, or a JSON string.

For organizational (and higher-level framework) purposes, you can define
several error-handling middleware functions, much as you would with
regular middleware functions. For example, to define an error-handler
for requests made by using XHR and those without:

const bodyParser = require('body-parser')
const methodOverride = require('method-override')

app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use(logErrors)
app.use(clientErrorHandler)
app.use(errorHandler)

In this example, the generic logErrors might write request and
error information to stderr, for example:

function logErrors (err, req, res, next) {
  console.error(err.stack)
  next(err)
}

Also in this example, clientErrorHandler is defined as follows; in this case, the error is explicitly passed along to the next one.

Notice that when not calling “next” in an error-handling function, you are responsible for writing (and ending) the response. Otherwise those requests will “hang” and will not be eligible for garbage collection.

function clientErrorHandler (err, req, res, next) {
  if (req.xhr) {
    res.status(500).send({ error: 'Something failed!' })
  } else {
    next(err)
  }
}

Implement the “catch-all” errorHandler function as follows (for example):

function errorHandler (err, req, res, next) {
  res.status(500)
  res.render('error', { error: err })
}

If you have a route handler with multiple callback functions you can use the route parameter to skip to the next route handler. For example:

app.get('/a_route_behind_paywall',
  (req, res, next) => {
    if (!req.user.hasPaid) {
      // continue handling this request
      next('route')
    } else {
      next()
    }
  }, (req, res, next) => {
    PaidContent.find((err, doc) => {
      if (err) return next(err)
      res.json(doc)
    })
  })

In this example, the getPaidContent handler will be skipped but any remaining handlers in app for /a_route_behind_paywall would continue to be executed.

Calls to next() and next(err) indicate that the current handler is complete and in what state. next(err) will skip all remaining handlers in the chain except for those that are set up to handle errors as described above.

 on
April 29, 2021

A Guide to Error Handling in Express.js

Error handling often doesn’t get the attention and prioritization it deserves. Especially for newbie developers, there is more focus on setting up routing, route handlers, business logic, optimizing performance, etc. As a result, the equally (if not more) crucial error-handling part will likely be overlooked. Striving for the most optimized code and squeezing out every last ounce of performance is all well and good; yet, it’s important to remember all it takes is one unhandled error leak into your user interface to override all the seconds you helped your users save.

Because there are so many components involved in a successful, functioning web application, it is vital to foolproof your application by preparing for all possible errors and exceptions. If left mishandled, these errors can lead to a bad user experience and end up affecting your business. At the same time, errors provide critical information about potential errors in your application that could bring the whole thing down. Therefore, you must be thoughtful and intelligent about error handling in your application. 

This post will c, Node.js’s most popular server-side framework (even though most of these concepts apply to other frameworks too). Express does a great job taking care of several unhandled errors and provides an easy-to-use, flexible API that developers can utilize to build error handling middleware. 

Here’s an outline of what we’ll be covering so you can easily navigate or skip ahead in the guide:

  • How does Error Handling Work in Express.js?
    • Express Middleware Functions
    • Default Error Handling in Express.js
  • Handling Custom Errors
    • Custom Handling for Each Route
    • Writing your own Error Handling Middleware Functions
    • Adding Multiple Middleware Handlers
  • Basic Quick Tutorial: Setting up Error Handling in Express.js

How Does Error Handling Work in Express.js? 

Express.js is the most popular Javascript server-side framework, perhaps, primarily because of its ease of usage and getting started. One of the many ways it makes things easier is by automatically catching all errors in route handlers, and allowing developers to extend route handling functionalities by leveraging useful middleware functions. 

Before we see how all of this works, let’s briefly visit the concept of middleware functions in Express – most error handling functionality is achieved through these functions. 

Express Middleware Functions 

Middleware functions in Express are essentially functions that come into play after the server receives the request and before the response fires to the client. They have access to the request and the response objects. They can be used for any data processing, database querying, making API calls, sending the response, or calling the next middleware function (using the next() function). 

Two aspects of middleware functions to keep in mind are:

  • They are triggered sequentially (top to bottom) based on their sequence in code.
  • They operate until the process exits, or the response has been sent back to the client.

Let’s understand this through a small example. Below we define two middleware functions using the .use() function and one route handler (skipping the boilerplate code for the sake of simplicity):

app.use((req, res, next) => {
  console.log("Middleware 1 called.")
  console.log(req.path)
  next() // calling next middleware function or handler
})

app.get('/', (req, res) => {
  console.log("Route handler called.")
  res.send("Hello world!") // response sent back – no more middleware called
})

app.use((req, res, next) => {
  console.log("Last middleware called❓") // not called
})

Here, each time the server receives a request, the first middleware is fired, followed by the corresponding route handler (using the next() function). However, because the response returns in this handler, the last middleware function is not called. Here’s the output:

undefined
Server output

Several native as well as third-party middleware functions have been made available by the Express community and are widely for adding functionalities like session management, authentication, logging, redirecting, and so much more. This was a basic example of how middleware functions work. We will come back to them when discussing how to utilize them for error handling in our applications.

Default Error Handling in Express.js 

Express implicitly takes care of catching your errors to prevent your application from crashing when it comes to error handling. This is especially true for synchronous route handler code. Let’s see how:

Synchronous Code

Synchronous code refers to statements of code that execute sequentially and one at a time. When an error encounters synchronous code, Express catches it automatically. Here’s an example of a route handler function where we simulate an error condition by throwing an error:

app.get('/', (req, res) => {
  throw new Error("Hello error!")
})

Express catches this error for us and responds to the client with the error’s status code, message, and even the stack trace (for non-production environments).

All of this is taken care of thanks to Express’s default built-in error handler middleware function inserted at the end of your code’s middleware stack. This automatic handling saves you from bulky try/catch blocks and explicit calls to the in-built middleware (shown below) while also providing some fundamental default error handling functionality. 

app.get('/', (req, res, next) => {
  try {
      throw new Error("Hello error!")
  }
  catch (error) {
      next(error)
  }
})

You can also choose to create your own middleware function to specify your error handling logic. 

Asynchronous Code

When writing server-side code, most of your route handlers are likely using asynchronous Javascript logic to read and write files on the server, query databases, and make external API requests. Let’s see whether Express can catch errors raised from asynchronous code as well. We’ll throw an error from inside the asynchronous setTimeout() function and see what happens:

app.get('/', (req, res) => {
  setTimeout(() => {
      console.log("Async code example.")
      throw new Error("Hello Error!")
  }, 1000)
})

As you can see, our server crashed because Express didn’t handle the error for us. 

undefined
Server output

For handling errors raised during asynchronous code execution in Express (versions < 5.x), developers need to themselves catch their errors and invoke the in-built error handler middleware using the next() function. Here’s how:

app.get('/', (req, res, next) => {
  setTimeout(() => {
      try {
          console.log("Async code example.")
          throw new Error("Hello Error!")
      } catch (error) { // manually catching
          next(error) // passing to default middleware error handler
      }
  }, 1000)
})
undefined
Browser output

This is much better – we caught the error, and our server didn’t crash. This does look a little bulky because we used the setTimeout() function to demonstrate async behavior. This function does not return a promise and, therefore, can’t be chained with a quick .catch() function. However, most libraries that help with async operations return promises these days (e.g., the file system API). Below is an example of a more convenient and common way of catching errors from promises:

const fsPromises = require('fs').promises
app.get('/', (req, res, next) => {
  fsPromises.readFile('./no-such-file.txt')

     .then(data => res.send(data))

     .catch(err => next(err)) 
})

Note: Express 5.0 (currently in alpha) can automatically catch errors (and rejections) thrown by returned Promises. 

Handling Custom Errors 

Express’s default error-handling middleware is super helpful for beginners to take care of unexpected, unhandled errors. However, different developers and organizations would want their errors handled in their own way – some might want to write these to log files, others might want to alert the user or redirect them to another page, or all of the above.

Custom Handling for Each Route 

An obvious, naive way of going about this would be to define your custom error handling logic for each route handler as so:

const express = require('express')
const fsPromises = require('fs').promises;

const app = express()
const port = 3000

app.get('/one', (req, res) => {
  fsPromises.readFile('./one.txt')
    .then(data => res.send(data))
    .catch(err => { // error handling logic 1
        console.error(err) // logging error
        res.status(500).send(err)
    })
})

app.get('/two', (req, res) => {
  fsPromises.readFile('./two.txt')
    .then(data => res.send(data))
    .catch(err => { // error handling logic 2
        console.error(err)
        res.redirect('/error') // redirecting user
    })
})

app.get('/error', (req, res) => {
  res.send("Custom error landing page.")
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

Here, we specified two different handling logics – one for each route that attempts to read arbitrary files on the server. As you can imagine, this would get too redundant quickly and wouldn’t scale well as you add more and more routes.

Writing your Error Handling Middleware Functions 

A much better option would be to leverage Express’s middleware functions here. You could write one or more middleware functions for handling errors in your application that all of your routes could utilize by making simple next() calls. 

Middleware functions are much more convenient to work with than conventional functions because they automatically have access to the error, request, and response objects and can be invoked (or invoke others) based on their ordering using just the next() function.

You can create your own error handling middleware functions by adding the error argument to the function, apart from request, response, and next. Here is an example:

app.use((error, req, res, next) => {
  console.log("Error Handling Middleware called")
  console.log('Path: ', req.path)
  next() // (optional) invoking next middleware
})

Another thing to keep in mind is the ordering of the middleware. The error handler needs to specify middleware functions after the route handlers for the next(error) calls to be directed towards them.

Now let’s recreate the previous example, but this time with an error-handling middleware in place.

const express = require('express')
const fsPromises = require('fs').promises

const app = express()
const port = 3000

app.get('/one', (req, res, next) => {
  fsPromises.readFile('./one.txt') // arbitrary file
    .then(data => res.send(data))
    .catch(err => next(err)) // passing error to custom middleware
})

app.get('/two', (req, res, next) => {
  fsPromises.readFile('./two.txt')
    .then(data => res.send(data))
    .catch(err => {
        err.type = 'redirect' // custom prop to specify handling behaviour
        next(err)
    })
})

app.get('/error', (req, res) => {
  res.send("Custom error landing page.")
})

app.use((error, req, res, next) => {
  console.log("Error Handling Middleware called")
  console.log('Path: ', req.path)
  console.error('Error: ', error)
 
  if (error.type == 'redirect')
      res.redirect('/error')

   else if (error.type == 'time-out') // arbitrary condition check
      res.status(408).send(error)
  else
      res.status(500).send(error)
})


app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

Instead of defining the handling behavior inside each route, we place all our logic inside the middleware. Then, based on the kind of error, we can modify the error object (or throw a custom error) and accordingly deal with it in the middleware.

This allows us to achieve the same functionality as before, but more efficiently. Assuming these files are not present on the server, if we go to /one, the server logs the error and sends back a 500 (internal server error) response. We are redirected to the /error page after the error logs if we open /two. Below are the corresponding client and server outputs:

As you can imagine, this was a fairly basic example just to give you a sense of how you can decouple your error handling logic from the route handling into a middleware function. This extends to larger applications with hundreds of routes for increased modularity, reduced redundancy, easier maintenance, and more efficient exception handling.

Adding Multiple Middleware Handlers 

In the previous section, we worked with just one middleware to handle all our errors. However, in practice, multiple middleware functions are usually employed for different aspects of error handling to have further abstractions. For example, one middleware for logging errors, another for responding to the client, perhaps another as a fail-safe catch-all handler, etc. Here’s a preview of the same based on our previous example:

// route handlers
app.get('/one')
app.get('/two') 

app.get('/error')
// middleware

app.use(errorLogger)
app.use(errorResponder)
app.use(failSafeHandler)

Let’s write the code for this.

const express = require('express')
const fsPromises = require('fs').promises

const app = express()
const port = 3000

app.get('/one', (req, res, next) => {
  fsPromises.readFile('./one.txt')
  .then(data => res.send(data))
  .catch(err => next(err)) // passing error to custom middleware
})

app.get('/two', (req, res, next) => {
  fsPromises.readFile('./two.txt')
  .then(data => res.send(data))
  .catch(err => {
      err.type = 'redirect' // adding custom property to specify handling behaviour
      next(err)
  })
})

app.get('/error', (req, res) => {
  res.send("Custom error landing page.")
})

function errorLogger(error, req, res, next) { // for logging errors
  console.error(error) // or using any fancy logging library
  next(error) // forward to next middleware
}

function errorResponder(error, req, res, next) { // responding to client
  if (error.type == 'redirect')
      res.redirect('/error')
  else if (error.type == 'time-out') // arbitrary condition check
      res.status(408).send(error)
  else
      next(error) // forwarding exceptional case to fail-safe middleware
}

function failSafeHandler(error, req, res, next) { // generic handler
  res.status(500).send(error)
}

app.use(errorLogger)
app.use(errorResponder)
app.use(failSafeHandler)

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})

This allows us to achieve the same functionality as in the previous code example, but in a more modular way that would scale better as you add more routes and handle more error conditions.

However, as previously discussed, when working with multiple middleware functions, one must keep an eye on their sequence and remember that each middleware should either respond to the client or invoke the subsequent one in the stack. If the server is just left to hang, the client continues to wait. For example, if we missed using next() in the first middleware (errorLogger), the subsequent middleware functions are not invoked, and therefore, no response fires. 

Basic Quick Tutorial: Setting up Error Handling in Express.js

Now that we’ve covered almost all aspects of error handling in Express, theory-wise, let’s solidify our understanding of these concepts by creating a prototype Express application that handles errors using middleware methods in a relatively more realistic setting.

We’ll create an API that serves user posts data fetched from a dummy API (jsonplaceholder.typicode.com). We will then validate some of the posts’ properties based on some arbitrary criteria (e.g., the content length), raise custom errors if validation fails, capture these using our custom middleware, and process them accordingly. 

Step 1: Create and Setup Project 

First, create an empty folder, cd into it, generate an npm project, and install the dependencies.

mkdir my-express-app && cd my-express-app
npm init -y
npm i --save express node-fetch

Then, create files – index.js, routes.js, errors.js, and middleware.js. It is considered good practice to keep your routes, main file, and other utilities in separate files. Ideally, developers prefer different folders for better organization, but for our small prototype, just files would suffice.

undefined
Project files

Step 2: Setup the Server 

Now let’s write the code that will start our server in index.js.

// index.js
const express = require('express')
const app = express()
const port = 3000

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})

We’ll start the server and make sure everything is working fine by running the node index.js command from inside the folder.

undefined
Console output

Step 3: Create Some Routes 

Now let’s create some routes in the routes.js file, and for now, just fetch some dummy JSON posts data from the dummy API (jsonplaceholder.typicode.com/posts), and serve it through our route. We will use Express’s Router module and export our routes – to import into our main index.js server file.

// routes.js
const express = require('express')
const fetch = require('node-fetch') // for making external API requests
const router = express.Router()
router.get('/', (req, res) => {
    res.send("Hello World!")
})

router.get('/user-posts', (req, res, next) => {
  fetch('https://jsonplaceholder.typicode.com/posts')
      .then(res => res.json())
      .then(data => {
          console.log(data)
          res.header("Content-Type",'application/json');
          res.send(JSON.stringify(data, null, 4)) // pretty print
        })
      .catch(err => next(err)) // pass to default error handler middleware
})

router.get('/error', (req, res) => {
  res.send("The URL you are trying to reach does not exist.")
})

module.exports = router // export routes

Now let’s import these routes into our server file.

// index.js
const express = require('express')
const routes = require('./routes') // importing routes

const app = express()
const port = 3000

app.use(routes) // initializing routes

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})

Let’s fire up our browser and check whether the route is working.

undefined
Browser output

Our server seems to be working fine here. Let’s do some error handling now.

Step 4: Creating and Handling Custom Errors 

It is pretty helpful to create custom error classes for your applications by extending Node’s Error class in practice. These errors can raise issues specific to the application, for example – unauthorized access, unsuccessful payment, incorrect user input, etc. This allows developers to have more detailed information about the error conditions (through custom error messages and other properties), and therefore handle them better.

In our use case, let’s say we want to ensure that all the posts have a title of fewer than 100 characters and a body character count of fewer than 220 characters. If we don’t meet this condition, we want to raise a custom error message that alerts the developer about the same time. 

Now that we have the error condition in mind, let’s create our custom error classes in the errors.js file.

// errors.js
class CharacterCountExceeded extends Error { // parent error
  constructor(post_id, content) {
      super();
      this.name = this.constructor.name // good practice

      if (this instanceof LongTitleError) // checking if title or body
          this.type = 'title'
      else if (this instanceof LongBodyError)
          this.type = 'body'
 
    this.message = `The character count of post (id: ${post_id}) ${this.type} is too long. (${content.length} characters)` // detailed error message
    this.statusCode = 500 // error code for responding to client
  }
}

// extending to child error classes
class LongTitleError extends CharacterCountExceeded { }
class LongBodyError extends CharacterCountExceeded { }

module.exports = {
    CharacterCountExceeded,
    LongTitleError,
    LongBodyError
}

First, we create one parent error class (CharacterCountExceeded) for all errors that involve an exceeded character count. The constructor for this class accepts the post’s ID and the content (of the title or body) to generate the required error message and specify an error code. Then we extend this class to create two more specific children classes (LongTitleError and LongBodyError) that refer to the particular error condition.

Now we will import these into our routes.js file, check for erroneous conditions inside our route handler, and throw these custom errors wherever required.

// routes.js
const express = require('express')
const fetch = require('node-fetch')
const router = express.Router()
const { LongTitleError, LongBodyError } = require('./errors');
router.get('/', (req, res) => {
  res.send("Hello World!")
})

router.get('/user-posts', (req, res, next) => {
  fetch('https://jsonplaceholder.typicode.com/posts')
      .then(res => res.json())
      .then(posts => {
          for (post of posts) {
              if (post.title.length > 100)
                  throw new LongTitleError(post.id, post.body)
              if (post.body.length > 220)
                  throw new LongBodyError(post.id, post.body) 
          }
          console.log(posts)
          res.header("Content-Type", 'application/json')
          res.send(JSON.stringify(posts, null, 4)) // pretty print
      })
      .catch(err => next(err))
})

router.get('/error', (req, res) => {
    res.send("The URL you are trying to reach does not exist.")
})

module.exports = router

As you can see here, we traverse through all the posts, check for their title and body’s character count, and throw our custom errors accordingly. Here’s the output:

undefined

It turns out there was one post that had a body size of more than 220 characters, and we successfully captured it. At the moment, we are forwarding all our errors through the catch block to Express’s default error handler middleware. But what’s the fun in that?

Let’s create our own middleware functions and use them as we like.

Step 5: Adding Custom Error Handler Middleware

We’ll use the middleware.js file that we created before.

// middleware.js
const errorLogger = (err, req, res, next) => {
  console.error('x1b[31m', err) // adding some color to our logs
  next(err) // calling next middleware
}

const errorResponder = (err, req, res, next) => {
  res.header("Content-Type", 'application/json')
  res.status(err.statusCode).send(JSON.stringify(err, null, 4)) // pretty print
}
const invalidPathHandler = (req, res, next) => {
  res.redirect('/error')
}

module.exports = { errorLogger, errorResponder, invalidPathHandler }

Here, we add three middleware functions – one for logging errors, one for sending the error to the client, and one for redirecting a user from an invalid route to an error landing page. Now let’s import these into our main file and use them in our application.

// index.js
const express = require('express')
const routes = require('./routes')
const { errorLogger, errorResponder, invalidPathHandler } = require('./middleware')

const app = express()
const port = 3000

app.use(routes)

// middleware
app.use(errorLogger)
app.use(errorResponder)
app.use(invalidPathHandler)

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})

Now let’s open our browser and see whether the middleware handles our LongBody error the way it’s supposed to.

undefined
Console output: Error object logged in red using the errorLogger middleware.
undefined
Server’s error response using the errorResponder middleware.
undefined
Redirecting to the error landing page upon encountering an invalid path

As you can see all our middleware functions are working as expected – in logging, responding to the client, and redirecting to the error landing page.

Wrapping it Up

We covered everything about error handling in Express.js – from default error handling of synchronous and asynchronous code to creating your own error classes and writing your own error-handling middleware functions. 

Now go ahead and make sure to handle all your errors in your Express application in a clean, non-redundant, efficient, and easy to maintain way. And if you haven’t already, write your own middleware functions and play around with native and third-party ones to explore how they can be helpful for your applications. If you are serious about your application’s performance and want to spend less time debugging issues and more time building new features, consider checking out ScoutAPM for monitoring your Node.js app’s performance and get started with a 14-day free trial.

6th Nov 2019

I had a hard time learning how to handle errors in Express when I started. Nobody seemed to have written the answers I needed, so I had to learn it the hard way.

Today, I want to share everything I know about handling errors in an Express app.

Let’s begin with synchronous errors.

Handling synchronous errors

If you want to handle a synchronous error, you can throw the error in an Express request handler. (Note: Request handlers are also called controllers. I prefer saying request handlers because they’re explicit and easy to understand).

app.post('/testing', (req, res) => {
  throw new Error('Something broke! 😱')
})

These errors can be caught with an Express error handler. If you did not write a custom error handler (more on this below), Express will handle the error for you with a default error handler.

Express’s default error handler will:

  1. Set the HTTP Status to 500
  2. Sends a text response to the requester
  3. Logs the text response in the console

Error returns to the client

Handling asynchronous errors

If you want to handle an asynchronous error, you need to send the error into an express error handler through the next argument.

app.post('/testing', async (req, res, next) => {
  return next(new Error('Something broke again! 😱'))
})

If you’re using Async/await in an Express app, you want to use a wrapper function like express-async-handler. This lets you write asynchronous code without try/catch blocks. I wrote more about this in “Using Async/await in Express”.

const asyncHandler = require('express-async-handler')

app.post(
  '/testing',
  asyncHandler(async (req, res, next) => {
    // Do something
  })
)

Once you wrapped the request handler with express-async-handler, you can throw the error as before, and it’ll be handled with an Express error handler.

app.post(
  '/testing',
  asyncHandler(async (req, res, next) => {
    throw new Error('Something broke yet again! 😱')
  })
)

Writing a custom error handler

Express error handlers take in four arguments:

  1. error
  2. req
  3. res
  4. next

They must be placed after all your middlewares and routes.

app.use(/*...*/)
app.get(/*...*/)
app.post(/*...*/)
app.put(/*...*/)
app.delete(/*...*/)

// Place your error handler after all other middlewares
app.use((error, req, res, next) => {
  /* ... */
})

Express will stop using its default error handler once you create a custom error handler. To handle an error, you need to communicate with the frontend that’s requesting the endpoint. This means you need to:

  1. Send over a valid HTTP status code
  2. Send over a valid response

A valid HTTP status code depends on what happened. Here’s a list of common errors you should prepare for:

  1. 400 Bad Request Error:
  • Used when user fails to include a field (like no credit card information in a payment form)
  • Also used when user enters incorrect information (Example: Entering different passwords in a password field and password confirmation field).
  1. 401 Unauthorized Error: Used when user enters incorrect login information (like username, email or password).
  2. 403 Forbidden Error: Used when user is not allowed access the endpoint.
  3. 404 Not Found Error: Used when the endpoint cannot be found.
  4. 500 Internal Server Error: Used the request sent by the frontend is correct, but there was an error from the backend.

Once you determined the correct HTTP status code, you want to set the status with res.status

app.use((error, req, res, next) => {
  // Bad request error
  res.status(400)
  res.json(/* ... */)
})

The HTTP status code should match the error message. For the status code to match the error message, you must send the status code together with the error.

The easiest way is to use the http-errors package. It lets you send three things in your errors:

  1. A status code
  2. A message to go with the error
  3. Any properties you’d like to send. This is optional.

Installing http-errors:

npm install http-errors --save

Using http-errors:

const createError = require('http-errors')

// Creating an error
throw createError(status, message, properties)

Let’s work through an example together to make it clearer. Let’s say you tried to find a user by their email address. The user cannot be found. You want to throw an error that says “User not found”.

When you create the error, you want to:

  1. Send a 400 Bad Request Error (because the user filled in incorrect information). You send this as the first parameter.
  2. Send a message that says “User not found”. You send this as the second parameter.
app.put(
  '/testing',
  asyncHandler(async (req, res) => {
    const { email } = req.body
    const user = await User.findOne({ email })

    // Throws error if user not found
    if (!user) throw createError(400, `User '${email}' not found`)
  })
)

You can get the status code with error.status and the error message with error.message.

// Logging the error
app.use((error, req, res, next) => {
  console.log('Error status: ', error.status)
  console.log('Message: ', error.message)
})

Status code and error message logged into the console.

Then, you set the error status with res.status. You send the message with res.json.

app.use((error, req, res, next) => {
  // Sets HTTP status code
  res.status(error.status)

  // Sends response
  res.json({ message: error.message })
})

Personally I like to send the status, the message, and the stack trace for me to debug easily.

app.use((error, req, res, next) => {
  // Sets HTTP status code
  res.status(error.status)

  // Sends response
  res.json({
    status: error.status,
    message: error.message,
    stack: error.stack
  })
})

Fallback status code

If the error did not originate from createError, it will not have a status property.

Here’s an example. Let’s say you tried to read a file with fs.readFile, but the file does not exist.

const fs = require('fs')
const util = require('util')

// Converts readFile from callbacks to Async/await.
// Find out how to do this here: https://zellwk.com/blog/converting-callbacks-to-promises/
const readFilePromise = util.promisify(fs.readFile)

app.get('/testing', asyncHandler(async (req, res, next) => {
  const data = await readFilePromise('some-file')
})

This error would not contain a status property.

app.use((error, req, res, next) => {
  console.log('Error status: ', error.status)
  console.log('Message: ', error.message)
})

Error does not contain the status property

In these cases, you can default to 500 Internal Server Error.

app.use((error, req, res, next) => {
  res.status(error.status || 500)
  res.json({
    status: error.status,
    message: error.message,
    stack: error.stack
  })
})

Changing an error’s status code

Let’s say you want to retrieve a file from a user’s input. If the file does not exist, you should throw a 400 Bad Request Error, because it’s not your server’s fault.

In this case, you want to use try/catch to catch the original error. Then, you recreate an error with createError.

app.get('/testing', asyncHandler(async (req, res, next) => {
  try {
    const { file } = req.body
    const contents = await readFilePromise(path.join(__dirname, file))
  } catch (error) {
    throw createError(400, `File ${file} does not exist`)
  }
})

Handling 404 errors

An endpoint is not found if a request falls through all your middlewares and routes.

To handle a Not Found Error, you insert a middleware between your routes and your error handler. Here, create an error with createError.

// Middlewares...
// Routes...

app.use((req, res, next) => {
  next(createError(404))
})

// Error handler...

Not found error sent to the client.

Don’t panic if you see an error that says “Cannot set headers after they’re sent to the server”.

Error: Cannot set headers after they're sent.

This error happens because the code ran methods that set response headers more than once in the same handler. These are the methods that set a response headers for you:

  1. res.send
  2. res.json
  3. res.render
  4. res.sendFile
  5. res.sendStatus
  6. res.end
  7. res.redirect

For example, if you run res.render and res.json in the same response handler, you will get the “Cannot set headers after they’re sent” error.

app.get('/testing', (req, res) => {
  res.render('new-page')
  res.json({ message: '¯_(ツ)_/¯' })
})

So, if you get this error, double-check your response handlers to make it doesn’t run the above methods twice.

When streaming

If an error occurs when you’re streaming a response to the frontend, you will get the same “Cannot set headers” error.

In this case, Express states you should delegate the error handling to the default Express handlers. It will send an error and close the connection for you.

app.use((error, req, res, next) => {
  // Do this only if you're streaming a response
  if (res.headersSent) {
    return next(error)
  }

  // Rest of the error handlers
})

That’s all I know for now! :)

If you enjoyed this article, please support me by sharing this article Twitter or buying me a coffee 😉. If you spot a typo, I’d appreciate if you can correct it on GitHub. Thank you!

Simon Plenderleith

Express provides a default error handler, which seems great until you realise that it will only send an HTML formatted error response. This is no good for your API as you want it to always send JSON formatted responses. You start handling errors and sending error responses directly in your Express route handler functions.

Before you know it, you have error handling code that’s logging errors in development to help you debug, and doing extra handling of the error object in production so you don’t accidentally leak details about your application’s internals. Even with just a few routes, your error handling code is getting messy, and even worse, it’s duplicated in each of your route handler functions. Argh!

Wouldn’t it be great if you could send JSON error responses from your API and have your error handling code abstracted into one place, leaving your route handlers nice and tidy? The good news is that you can, by creating your own error handler middleware.

In this article you’ll learn how to create an error handler middleware function which behaves in a similar way to Express’ default error handler, but sends a JSON response. Just the error handler that your API needs!

Jump links

  • Getting errors to error handler middleware
  • Creating an error handler

    • Error handler concerns
    • Error handler middleware function
    • Error handler helper functions
  • Applying the error handler middleware

    • Example error response
  • Next steps

Getting errors to error handler middleware

The Express documentation has examples of an error being thrown e.g. throw new Error('..'), however this only works well when all of your code is synchronous, which is almost never in Node.js. If you do throw error objects in your Express application, you will need to be very careful about wrapping things so that next() is always called and that the error object is passed to it.

There are workarounds for error handling with asynchronous code in Express – where Promise chains are used, or async/await – however the fact is that Express does not have proper support built in for asynchronous code.

Error handling in Express is a broad and complex topic, and I plan to write more about this in the future, but for the purpose of this article we’ll stick with the most reliable way to handle errors in Express: always explicitly call next() with an error object e.g.

app.get("/user", (request, response, next) => {
    const sort = request.query.sort;

    if (!sort) {
        const error = new error("'sort' parameter missing from query string.");

        return next(error);
    }

    // ...
});

Enter fullscreen mode

Exit fullscreen mode

Creating an error handler

You can create and apply multiple error handler middleware in your application e.g. one error handler for validation errors, another error handler for database errors, however we’re going to create a generic error handler for our API. This generic error handler will send a JSON formatted response, and we’ll be applying the best practices that are detailed in the official Express Error handling guide. If you want, you’ll then be able to build on this generic error handler to create more specific error handlers.

Ok, let’s get stuck in!

Error handler concerns

Here are the things we’re going to take care of with our error handler middleware:

  • Log an error message to standard error (stderr) – in all environments e.g. development, production.
  • Delegate to the default Express error handler if headers have already been sent – The default error handler handles closing the connection and failing the request if you call next() with an error after you’ve started writing the response, so it’s important to delegate to the default error handler if headers have already been sent (source).
  • Extract an error HTTP status code – from an Error object or the Express response object.
  • Extract an error message – from an Error object, in all environments except production so that we don’t leak details about our application or the servers it runs on. In production the response body will be empty and the HTTP status code will be what clients use to determine the type of error that has occurred.
  • Send the HTTP status code and the error message as a response – the body will be formatted as JSON and we’ll send a Content-Type: application/json header.
  • Ensure remaining middleware is run – we might end up adding middleware after our error handler middleware in future e.g. to send request timing metrics to another server, so it’s important that our error handler middleware calls next(), otherwise we could end up in debugging hell in the future.

Error handler middleware function

In Express, error handling middleware are middleware functions that accept four arguments: (error, request, response, next). That first error argument is typically an Error object which the middleware will then handle.

As we saw above, there are quite a few concerns that our error handler needs to cover, so let’s first take a look at the error handler middleware function. Afterwards we’ll dig into the helper functions which it calls.

// src/middleware/error-handler.js

const NODE_ENVIRONMENT = process.env.NODE_ENV || "development";

/**
 * Generic Express error handler middleware.
 *
 * @param {Error} error - An Error object.
 * @param {Object} request - Express request object
 * @param {Object} response - Express response object
 * @param {Function} next - Express `next()` function
 */
function errorHandlerMiddleware(error, request, response, next) {
    const errorMessage = getErrorMessage(error);

    logErrorMessage(errorMessage);

    /**
     * If response headers have already been sent,
     * delegate to the default Express error handler.
     */
    if (response.headersSent) {
        return next(error);
    }

    const errorResponse = {
        statusCode: getHttpStatusCode({ error, response }),
        body: undefined
    };

    /**
     * Error messages and error stacks often reveal details
     * about the internals of your application, potentially
     * making it vulnerable to attack, so these parts of an
     * Error object should never be sent in a response when
     * your application is running in production.
     */
    if (NODE_ENVIRONMENT !== "production") {
        errorResponse.body = errorMessage;
    }

    /**
     * Set the response status code.
     */
    response.status(errorResponse.statusCode);

    /**
     * Send an appropriately formatted response.
     *
     * The Express `res.format()` method automatically
     * sets `Content-Type` and `Vary: Accept` response headers.
     *
     * @see https://expressjs.com/en/api.html#res.format
     *
     * This method performs content negotation.
     *
     * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation
     */
    response.format({
        //
        // Callback to run when `Accept` header contains either
        // `application/json` or `*/*`, or if it isn't set at all.
        //
        "application/json": () => {
            /**
             * Set a JSON formatted response body.
             * Response header: `Content-Type: `application/json`
             */
            response.json({ message: errorResponse.body });
        },
        /**
         * Callback to run when none of the others are matched.
         */
        default: () => {
            /**
             * Set a plain text response body.
             * Response header: `Content-Type: text/plain`
             */
            response.type("text/plain").send(errorResponse.body);
        },
    });

    /**
     * Ensure any remaining middleware are run.
     */
    next();
}

module.exports = errorHandlerMiddleware;

Enter fullscreen mode

Exit fullscreen mode

Error handler helper functions

There are three helper functions which are called by our error handler middleware function above:

  • getErrorMessage()
  • logErrorMessage()
  • getHttpStatusCode()

The benefit of creating these individual helper functions is that in future if we decide to create more specific error handling middleware e.g. to handle validation errors, we can use these helper functions as the basis for that new middleware.

Each of these helper functions are quite short, but they contain some important logic:

// src/middleware/error-handler.js

/**
 * Extract an error stack or error message from an Error object.
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
 *
 * @param {Error} error
 * @return {string} - String representation of the error object.
 */
function getErrorMessage(error) {
    /**
     * If it exists, prefer the error stack as it usually
     * contains the most detail about an error:
     * an error message and a function call stack.
     */
    if (error.stack) {
        return error.stack;
    }

    if (typeof error.toString === "function") {
        return error.toString();
    }

    return "";
}

/**
 * Log an error message to stderr.
 *
 * @see https://nodejs.org/dist/latest-v14.x/docs/api/console.html#console_console_error_data_args
 *
 * @param {string} error
 */
function logErrorMessage(error) {
    console.error(error);
}

/**
 * Determines if an HTTP status code falls in the 4xx or 5xx error ranges.
 *
 * @param {number} statusCode - HTTP status code
 * @return {boolean}
 */
function isErrorStatusCode(statusCode) {
    return statusCode >= 400 && statusCode < 600;
}

/**
 * Look for an error HTTP status code (in order of preference):
 *
 * - Error object (`status` or `statusCode`)
 * - Express response object (`statusCode`)
 *
 * Falls back to a 500 (Internal Server Error) HTTP status code.
 *
 * @param {Object} options
 * @param {Error} options.error
 * @param {Object} options.response - Express response object
 * @return {number} - HTTP status code
 */
function getHttpStatusCode({ error, response }) {
    /**
     * Check if the error object specifies an HTTP
     * status code which we can use.
     */
    const statusCodeFromError = error.status || error.statusCode;
    if (isErrorStatusCode(statusCodeFromError)) {
        return statusCodeFromError;
    }

    /**
     * The existing response `statusCode`. This is 200 (OK)
     * by default in Express, but a route handler or
     * middleware might already have set an error HTTP
     * status code (4xx or 5xx).
     */
    const statusCodeFromResponse = response.statusCode;
    if (isErrorStatusCode(statusCodeFromResponse)) {
        return statusCodeFromResponse;
    }

    /**
     * Fall back to a generic error HTTP status code.
     * 500 (Internal Server Error).
     *
     * @see https://httpstatuses.com/500
     */
    return 500;
}

Enter fullscreen mode

Exit fullscreen mode

Now that we’ve created our error handler middleware, it’s time to apply it in our application.

Applying the error handler middleware

Here is a complete example Express API application. It uses the http-errors library to add an HTTP status code to an error object and then passes it to the next() callback function. Express will then call our error handler middleware with the error object.

// src/server.js

const express = require("express");
const createHttpError = require("http-errors");

const errorHandlerMiddleware = require("./middleware/error-handler.js");

/**
 * In a real application this would run a query against a
 * database, but for this example it's always returning a
 * rejected `Promise` with an error message.
 */
function getUserData() {
    return Promise.reject(
        "An error occurred while attempting to run the database query."
    );
}

/**
 * Express configuration and routes
 */

const PORT = 3000;
const app = express();

/**
 * This route demonstrates:
 *
 * - Catching a (faked) database error (see `getUserData()` function above).
 * - Using the `http-errors` library to extend the error object with
 * an HTTP status code.
 * - Passing the error object to the `next()` callback so our generic
 * error handler can take care of it.
 */
app.get("/user", (request, response, next) => {
    getUserData()
        .then(userData => response.json(userData))
        .catch(error => {
            /**
             * 500 (Internal Server Error) - Something has gone wrong in your application.
             */
            const httpError = createHttpError(500, error);

            next(httpError);
        });
});

/**
 * Any error handler middleware must be added AFTER you define your routes.
 */
app.use(errorHandlerMiddleware);

app.listen(PORT, () =>
    console.log(`Example app listening at http://localhost:${PORT}`)
);

Enter fullscreen mode

Exit fullscreen mode

You can learn how to use the http-errors library in my article on ‘How to send consistent error responses from your Express API’.

Example error response

Here’s an example GET request with cURL to our /user endpoint, with the corresponding error response generated by our error handler middleware (in development):

$ curl -v http://localhost:3000/user

> GET /user HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.68.0
> Accept: */*
> 
< HTTP/1.1 500 Internal Server Error
< X-Powered-By: Express
< Vary: Accept
< Content-Type: application/json; charset=utf-8
< Content-Length: 279
< Connection: keep-alive
< 

{"message":"InternalServerError: An error occurred while attempting to run the database query.n at /dev/example/src/server.js:262:22n at processTicksAndRejections (internal/process/task_queues.js:97:5)"}

Enter fullscreen mode

Exit fullscreen mode

Next steps

You might have noticed that we’re not sending a response body in production. This is due to the fact that sending the error object’s message or call stack would leak details about our application, making it vulnerable to potential attackers. As we’ve created a generic error handler middleware here, the best that we can do is send back a suitable error HTTP status code in production.

If you know the types of the errors that your error handler middleware will be receiving (which you can check for example with error instanceof ErrorClass), you could define some production safe error messages which correspond with those error types. These production safe error messages could then be sent in the response body, providing more useful context about the error which has occurred. Give it a try!

Introduction

Exceptions and errors are bound to occur while users interact with any application, it is up to software engineers to choose a means to handle any error that might arise — knowingly or unknowingly. As a result, backend developers who build APIs with Express find themselves working to ensure that they are building a useful, efficient, and usable API. What is of most importance is to handle errors in such a way as to build a robust system because this helps to reduce development time, outright errors, productivity issues, and determines the success or scalability of software development.

Do you need to log the error message, suppress the error, notify users about the error, or write code to handle errors? Wonder no more.

In this guide, we will learn how to build a robust error-handling codebase for Express applications, which will serve in helping to detect application errors and take optimal actions to recover any application from gracefully failing during runtime.

Note: We’ll be using Postman to test the API in our demo. You can download it on the Postman Download page. Alternatively, you can simply use the browser, the command-line curl tool, or any other tool you might be familiar with.

What is Error Handling?

In software development, there are two different kinds of exceptions: operational and programmatic.

  • Operational failures might arise during runtime, and in order to prevent the application from terminating abruptly, we must gracefully handle these exceptions through efficient error handling methods.
  • Programmatic exceptions are thrown manually by a programmer, when an exceptional state arises.

You can think of operational exceptions as «unexpected, but foreseen» exceptions (such as accessing an index out of bounds), and programmatic exceptions as «expected and foreseen» exceptions (such as a number formatting exception).

Exception handling is the procedure used to find and fix flaws within a program. Error handling sends messages that include the type of error that happened and the stack where the error happened.

Note: In computer science, exceptions are recoverable from, and typically stem from either operational or programmatic issues during runtime. Errors typically arise form external factors, such as hardware limitations, issues with connectivity, lack of memory, etc. In JavaScript, the terms are oftentimes used interchangeably, and custom exceptions are derived from the Error class. The Error class itself represents both errors and exceptions.

In Express, exception handling refers to how Express sets itself up to catch and process synchronous and asynchronous exceptions. The good thing about exception handling in Express is that as a developer, you don’t need to write your own exception handlers; Express comes with a default exception handler. The exception handler helps in identifying errors and reporting them to the user. It also provides various remedial strategies and implements them to mitigate exceptions.

While these might seem like a lot of stuff going under the hood, exception handling in Express doesn’t slow the overall process of a program or pause its execution.

Understanding Exception Handling in Express

With the default error handler that comes with Express, we have in our hands a set of middleware functions that help to catch errors in route handlers automatically. Soon, we will create a project to put theory into practice on how to return proper errors in an Express app and how not to leak sensitive information.

Defining middleware function in Express

The error-handling middleware functions are defined in such a way that they accept an Error object as the first input parameter, followed by the default parameters of any other middleware function: request, response, and next. The next() function skips all current middleware to the next error handler for the router.

Setting up Error Handling in Express

Run the following command in your terminal to create a Node and Express app:

$ mkdir error-handling-express

In the newly created folder, let’s initialize a new Node project:

$ cd error-handling-express && npm init -y

This creates a package.json file in our folder.

To create an Express server in our Node app, we have to install the express package, dotenv for automatically loading environment variables into .env file into process.env object, and nodemon for restarting the node app if a file change is noted in the directory.

$ npm install express dotenv nodemon

Next, create an app.js file in the project folder which will serve as the index file for the app.

Now that we have installed all the needed dependencies for our Express app, we need to set up the script for reading the app in the package.json file. To achieve that, the package.json file, so that the scripts object is like shown below:

"scripts": {
    "start": "nodemon app.js"
},

Alternatively, you can skip using nodemon, and use node app.js instead.

Setting up an Express server

To set up the server, we have to first import the various packages into app.js. We will also create a .env file in the project directory — to store all environment variables for the application:

// app.js

const express = require('express')
require('dotenv').config
//.env
PORT=4000 

We have defined the port number for the app in .env, which is loaded in and read by dotenv, and can be accessed later.

Initializing the Express Server

Now, we need to initialize the Express server and make our app listen to the app port number, along with a request to a test route — /test. Let’s update app.js, beneath the import statements:

// app.js
const app = express();
const port = process.env.PORT || 4000;

app.get("/test", async (req, res) => {
    return res.status(200).json({ success: true });
});

app.listen(port, () => {
    console.log(`Server is running at port ${port}`);
});

From here on, we will learn how to handle various use cases of operational errors that can be encountered, in Express.

Handling Not Found Errors in Express

Suppose you need to fetch all users from a database of users, you can efficiently handle a potential error scenario where no data exist in the database, by wrapping the logic into a try/catch block — hoping to catch any error that could project in the catch block:

//app.js
const getUser = () => undefined;

app.get("/get-user", async (req, res) => {
	try {
		const user = getUser();
		if (!user) {
			throw new Error('User not found');
		}
	} catch (error) {
	    // Logging the error here
		console.log(error); 
		// Returning the status and error message to client
		res.status(400).send(error.message) 
	}
	return res.status(200).json({
		success: true
	});
});

This results in:

User not found

Now, when this request is made (you can test using Postman) and no user exists on the database, the client receives an error message that says «User not found». Also, you will notice that the error is logged in the console, too.

Optimizing Error Handling with Error Handler Middleware

We can optimize development by creating an error handler middleware that would come at the end of all defined routes, so that if an error is thrown in one of the routes, Express will automatically have a look at the next middleware and keep going down the list until it reaches the error handler. The error handler will process the error and also send back a response to the client.

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

To get started, create a folder called middleware in the project directory, and in this folder, create a file called errorHandler.js which defines the error handler:

const errorHandler = (error, req, res, next) => {
    // Logging the error here
    console.log(error); 
    // Returning the status and error message to client
    res.status(400).send(error.message); 
}
module.exports = errorHandler;

In our middleware function, we have made Express aware that this is not a basic middleware function, but an error handler, by adding the error parameter before the 3 basic parameters.

Now, we’ll use the error handler in our demo app.js and handle the initial error of fetching users with the error handler middleware, like shown below:

// app.js
const getUser = () => undefined;

app.get("/get-user", async (req, res, next) => {
	try {
		const user = getUser();
		if (!user) {
			throw new Error("User not found");
		}
	} catch (error) {
		return next(error);
	}
});

app.use(errorHandler);

We can optimize our code even more, by creating an abtsraction around the try/catch logic. We can achieve this by creating a new folder in the project directory called utils, and in it, create a file called tryCatch.js.

To abstract the try-catch logic — we can define a function that accepts another function (known as the controller) as its parameter, and returns an async function which will hold a try/catch for any receieved controller.

If an error occurs in the controller, it is caught in the catch block and the next function is called:

// tryCatch.js
const tryCatch = (controller) => async (req, res, next) => {
	try {
		await controller(req, res);
	} catch (error) {
		return next(error);
	}
};
module.exports = tryCatch;

With the try/catch abstraction, we can refactor our code to make it more succint by skipping the try-catch clause explicitly when fetching users in the app.js:

// app.js
const getUser = () => undefined;

app.get(
	"/get-user",
	tryCatch(async (req, res) => {
		const user = getUser();
		if (!user) {
			throw new Error("User not found");
		}
		res.status(400).send(error.message);
	})
);

We have successfully abstracted away the try-catch logic and our code still works as it did before.

Handling Validation Errors in Express

For this demo, we will create a new route in our Express app for login — to validate a user ID upon log in. First, we will install the joi package, to help with creating a schema, with which we can enforce requirements:

$ npm i joi

Next, create a schema which is a Joi.object with a userId which must be a number and is required — meaning that the request must match an object with a user ID on it.

We can use the validate() method in the schema object to validate every input against the schema:

// app.js
const schema = Joi.object({
	userId: Joi.number().required(),
});

app.post(
	"/login",
	tryCatch(async (req, res) => {
		const {error, value} = schema.validate({});
		if (error) throw error;
	})
);

If an empty object is passed into the validate() method, the error would be gracefully handled, and the error message would be sent to the client:

On the console, we also get access to a details array which includes various details about the error that could be communicated to the user if need be.

To specifically handle validation errors in such a way as to pass the appropriate error detail per validation error, the error handler middleware can be refactored:

// errorHandler.js
const errorHandler = (error, req, res, next) => {
	console.log(error); // logging the error here

	if (error.name === "ValidationError") {
		return res.status(400).send({
			type: "ValidationError",
			details: error.details,
		});
	}

	res.status(400).send(error.message); // returning the status and error message to client
};

module.exports = errorHandler;

With errorHandler.js now customized, when we make the same request with an empty object passed to the validate() method:

We now have access to a customized object that returns messages in a more readable/friendly manner. In this way, we are able to send and handle different kinds of errors based on the kind of error coming in.

Conclusion

In this guide, we went over every aspect of Express.js’ error handling, including how synchronous and asynchronous code is handled by default, how to make your own error classes, how to write custom error-handling middleware functions and provide next as the final catch handler

As with every task out there, there are also best practices during development which includes effective error handling, and today we have learned how we can handle errors in an Express app in a robust manner.

Handling errors properly doesn’t only mean reducing the development time by finding bugs and errors easily but also developing a robust codebase for large-scale applications. In this guide, we have seen how to set up middleware for handling operational errors. Some other ways to improve error handling includes: not sending stack traces, stopping processes gracefully to handle uncaught exceptions, providing appropriate error messages, sending error logs, and setting up a class that extends the Error class.

I hope the examples I used in this tutorial were enjoyable for you. I covered various scenarios you could potentially encounter when writing an Express application for use in the real world about error management. Please, let me know if there is anything I missed. It will benefit us and help me learn more as well. Have a good day and thanks for reading.

You can refer to all the source code used in the article on Github.

Additional Resources

  • Express.js Documentation

Home  »  Express   »   Express JS Error Handling Tutorial with Best Examples

In this Express JS tutorial, we’ll briefly go over what express js is, how to install it and the most importantly how to handle errors in an Express app. Express.js allows us to catch and process both synchronous and asynchronous errors efficiently for any unexpected situation. Express offers middleware functions to deal with errors.

Table of contents

  1. Introduction Express JS
  2. Setting Up Express JS Project with Node
  3. Basic Express Error Handling Example
  4. Define Error Handling in Express App
  5. Express Wildcard 404 Error Handling
  6. Not to Be Followed Error Handling Approach in Express
  7. Best Approach For Error Handling in Express
  8. Using Error Handling with Async/Await
  9. Conclusion

Introduction Express Js

Express.js is a web application framework based on Node.js. The core philosophy of Express is based on Http Modules and connect components, which are known as middleware. Express is designed to build a robust web application and RESTful APIs. Express.js provides a high level of flexibility to web developers when it comes to building the latest web applications.

Setting Up Express JS Project with Node

Run command in terminal to create a basic Node and Express js project from scratch:

mkdir expressjs-error-handling

Enter into the project folder:

cd expressjs-error-handling

Open the project folder in the code editor.

Run the below command to create the package.json file for Express.JS error handling project.

npm init -y

Following will be the output displayed on your terminal:

{
  "name": "expressjs-error-handling",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Setting up Express Server

In this step, we set up express js server, but before that install Express js package by running the given below command:

npm install express

Now, create an app.js file in the project’s root folder. In this file, we are about to keep our app’s basic configurations.

touch app.js

Then, add the Express server configurations:

// app.js
const express = require('express'),
  PORT = process.env.PORT || 4000;
// Express Configuration
const app = express();
// Set up PORT
app.listen(PORT, () => {
  console.log('Connected to port ' + PORT)
})

Your node server is ready to be served, save your app.js file and run the following command.

node app
# node app
# Server is running at PORT: 4000

You can check out your basic express app on the following url: http://localhost:4000

Basic Express Error Handling Example

A web application is made of various services and databases. When a web application receives the data, that data is processed through various services in the backend. While the process is going on, any unexpected thing may occur. A database or service might get failed. A web application should be ready to handle such kind of situation, and this is where the error handlers come into the limelight.

Let us check out the basic error handling example in Express.

// Express middleware
app.get('/employees', async function(req, res) {
  let employees;
  try {
    employees = await db.collection('Employees').find().toArray();
  } catch (error) {
    // User friendly error response
    res.status(500).json({ 
      error: error.toString() 
    });
  }
  res.json({ employees });
});

To give you the demo of how does error handling work in Express. We declared the '/employees' endpoint. When users access '/employees' endpoint, then the try-catch block will be triggered. And, If any error shows up, then, it will throw the exception; otherwise, the employee’s data will be returned. This is just a brief error handling example for handling one or two endpoints in an Express middleware.

Define Error Handling in Express App

Now, we’ll learn to integrate error handling using Express functions which are known as middleware. A req, res, and next arguments are denoted by middleware in Express. In the below example, we’ll see how to use middleware to handle the errors.

app.use((err, req, res, next) => {
  res.status(500).send('Error found');
});

Express Wildcard 404 Error Handling

We can also define error handling for wildcard routes using '*' in our routes if a user tries to access the route which doesn’t exist in our Express app. Express will throw the error or in simple terms will be triggered a 404 error in this case.

app.get('*', function(req, res, next) {
  let err = new Error("Page Doesn't Exist");
  err.statusCode = 404;
  next(err);
});

However, we can make it better, by telling Express that a user should receive a 404 error and also must be redirected to the custom error page.

app.use(function(err, req, res, next) {
  console.error(err.message);
  
  // Define a common server error status code if none is part of the err.
  if (!err.statusCode) err.statusCode = 500; 
  if (err.shouldRedirect) {
    // Gets a customErrorPage.html.
    res.render('customErrorPage')
  } else {
    // Gets the original err data, If shouldRedirect is not declared in the error.
    res.status(err.statusCode).send(err.message);
  }
});

Not to Be Followed Error Handling Approach in Express

If we throw the errors asynchronously in the same function, then It’ll inevitably crash the server every time an HTTP request is made. Due to the JavaScript’s default asynchronous nature.

// app.js
const express = require('express'),
  PORT = process.env.PORT || 4000;
// Express settings
const app = express();
// Express Error Handling
app.get('*', (req, res, next) => {
  setImmediate(() => {
    throw new Error('Something is really not good!');
  });
});
app.use((error, req, res, next) => {
  // This error can't be displayed
  res.json({
    message: error.message
  });
});
// Set up PORT
app.listen(PORT, () => {
  console.log('Connected to port ' + PORT)
})

Best Approach For Error Handling in Express

Now, I’ll show you the best approach to handle errors in Express js. The next middleware is helpful when it comes to defining errors accurately in Express. This argument is passed in the route handler, as mentioned below.

// app.js
const express = require('express'),
  PORT = process.env.PORT || 4000;
// Express settings
const app = express();
// Express Error Handling
app.get('*', (req, res, next) => {
  // Error goes via `next()` method
  setImmediate(() => {
    next(new Error('Something went wrong'));
  });
});
app.use((error, req, res, next) => {
  // Error gets here
  res.json({
    message: error.message
  });
});
// Set up PORT
app.listen(PORT, () => {
  console.log('Connected to port ' + PORT)
})

The vital point to be considered here is that the Express js middlewares execute in an orderly manner, we must define the error handlers after all the other middleware functions. If you don’t follow this process, then your handlers won’t work as expected.

Using Error Handling with Async/Await

Most of the Express js was created before EcmaScript 6 in between year 2011 till 2014. I know this is not the best approach to handle errors with async/await. However, error handling can also be made possible with JavaScript’s async/await approach with the help of a custom asyncHelper middleware function.

// app.js
const express = require('express'),
  PORT = process.env.PORT || 4000;
// Express settings
const app = express();
// Error Handling Helper Function
function asyncHelper(fn) {
  return function (req, res, next) {
    fn(req, res, next).catch(next);
  };
}
// Express Error Handling async/await
app.get('*', asyncHelper(async (req, res) => {
  await new Promise(resolve => setTimeout(() => resolve(), 40));
  throw new Error('Error found');
}));
app.use((error, req, res, next) => {
  res.json({
    message: error.message
  });
});
// Set up PORT
app.listen(PORT, () => {
  console.log('Connected to port ' + PORT)
})

As you can see in the example, we bind a custom helper function asyncHelper with async/await. It is playing a vital role in Express error handling as a middleware. The async function returns a promise, we used the .catch() method to get the errors and pass it to the next() method.

Conclusion

Finally, we have completed the Express error handling tutorial with real-world examples. I hope you liked the examples I mentioned in this tutorial. I have talked about every possible situation of error-handling you might face while developing a real-world express application. However, if I have skipped anything do let me know. It will help us to enhance my knowledge as well. Thanks for reading, have a good day.

Recommended Posts:

Понравилась статья? Поделить с друзьями:
  • Exposure head error
  • Export error premiere pro
  • Export error bcp execution
  • Explorer открывает edge как исправить
  • Explorer unknown hard error что делать виндовс 10