DEV Community

Cover image for Central Error Handling in Express
Chinedu Orie
Chinedu Orie

Posted on • Edited on

Central Error Handling in Express

Express is a fast, unopinionated, minimalist web framework for Node.js - docs

Error handling is a routine that one can not do without while building an application either in Express or in any other language.

When building API endpoints using Express or any other framework/library, validation checks are always necessary for each use case, and there's always a need to return an error response to the user. Handling these errors and returning a response for every validation becomes very tedious and makes the codebase messy.
Let's consider an example below:

const validateUser = async (req, res, next) => {
  try {
    const { email, password } = req.body
    if (!email || !password) {
      return res.status(400).json({
        status: 'error',
        message: 'Missing required email and password fields',
      })
    }
    const user = await db.User.findOne({ where: { email }});
    if (!user) {
      return res.status(404).json({
        status: 'error',
        message: 'User with the specified email does not exists',
      })
    }
    next()
  } catch (error) {
    return res.status(500).json({
      status: 'error',
      message: 'An error occurred trying to process your request',
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

Looking at the snippet above, you'd agree with me that it's already looking messy with the returning of error response at every checkpoint. If only that's the code in your codebase, then it wouldn't matter, the matter arises when you have to repeat the same approach across several methods or functions in your codebase.

Before we dive deep to finding a solution to make the snippet above better, let's look at what we need to have for this article:

Requirements

  • NodeJs installed
  • npm or yarn installed
  • Knowledge of Nodejs/Express

Note: This article assumes that the reader already has a basic knowledge of NodeJs/Express. Hence some details may be skipped.

To follow along, clone the repository used for this article here.

Step 1. Create a custom **Error* constructor*
We need to create a custom Error constructor which extends the JavaScript Error constructor.

In the project you cloned earlier, create a directory called helpers. Inside the helpers directory, create a file called error.js

Add the snippet below into the error.js

class ErrorHandler extends Error {
  constructor(statusCode, message) {
    super();
    this.statusCode = statusCode;
    this.message = message;
  }
}
module.exports = {
  ErrorHandler
}
Enter fullscreen mode Exit fullscreen mode

Notice that we exported the ErrorHandler so that we can import it from the index.js file.

Next up, we need to create a function for returning a formatted error response to the user.

Add the snippet below into the error.js file.

const handleError = (err, res) => {
  const { statusCode, message } = err;
  res.status(statusCode).json({
    status: "error",
    statusCode,
    message
  });
};
Enter fullscreen mode Exit fullscreen mode

Update the module.exports block to contain the handleError function as show below:

module.exports = {
  ErrorHandler,
  handleError
}
Enter fullscreen mode Exit fullscreen mode

Step 2. Create the Error-handling middleware

Middleware functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle.
The next middleware function is commonly denoted by a variable named next. Express docs

The error-handling middleware is a special type of middleware that accepts four arguments as opposed to a regular middleware. The first argument is the error object.
The snippet below shows an example of an error-handling middleware:

function(err, req, res, next) {
  //code goes here
}
Enter fullscreen mode Exit fullscreen mode

In the index.js, let's add an error-handling middleware, before then, let's import the handleError function inside the index.js;

The index.js file should look like shown below:

const express = require('express')
const { handleError } = require('./helpers/error')
const app = express()

app.use(express.json())
const PORT = process.env.PORT || 3000

app.get('/', (req, res) => {
  return res.status(200).json('Hello world'); 
})

app.use((err, req, res, next) => {
  handleError(err, res);
});
app.listen(PORT, () => console.log(`server listening at port ${PORT}`))
Enter fullscreen mode Exit fullscreen mode

Notice how we called the handleError function passing the error object and the response object to it.

Note: The error-handling middleware must be the last among other middleware and routes for it to function correctly.

Now anywhere in the application that you want to check for error, all you need to do is to throw the ErrorHandler constructor.
We can now apply the error-handling mechanism to refactor the messy code we had earlier. It should look like shown below:

const validateUser = async (req, res, next) => {
  try {
    const { email, password } = req.body
    if (!email || !password) {
      throw new ErrorHandler(404, 'Missing required email and password fields')
    }
    const user = await  db.User.findOne({ where: { email }});
    if (!user) {
      throw new ErrorHandler(404, 'User with the specified email does not exists')
    }
    next()
  } catch (error) {
    next(error)
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice how we passed the error to the next function above. What that simply does is to pass the error to the error-handling middleware we defined in index.js.

Let's add a route to test our error-handling mechanism that we just created. In the index.js add the snippet below:

app.get('/error', (req, res) => {
  throw new ErrorHandler(500, 'Internal server error');
})
Enter fullscreen mode Exit fullscreen mode

Remember to import the ErrorHandler in index.js. It should look like shown below:

const { handleError, ErrorHandler } = require('./helpers/error')
Enter fullscreen mode Exit fullscreen mode

Start the server by running, npm start and then visit the route /error. You'd get a response similar to the one shown below:

{
    "status": "error",
    "statusCode": 500,
    "message": "Internal server error"
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this article, we've established the need to have a central error handler in our Express application. We also demonstrated the steps that we need to take to implement a central error handler.

If you have any question or contributions to make this article better, kindly reach out to me via Twitter.
Thanks for reading through. ✌️

Oldest comments (40)

Collapse
 
ogwurujohnson profile image
Johnson Ogwuru

This is some good stuff, mate

Collapse
 
nedsoft profile image
Chinedu Orie

Thanks Chief

Collapse
 
mr_cea profile image
Ogbonna Basil • Edited

This is cool,

Just one opinion, i feel it should be expanded to be not just Error Handler but statusHandler

```
class statusHandler extends Error {
constructor(statusCode, message, data) {
super();
this.statusCode = statusCode;
this.message = message;
this.data = data || null

}
}
module.exports = {
statusHandler
}




Likewise for handleError but generally this approach is effectively DRY. Nice one
Collapse
 
nedsoft profile image
Chinedu Orie

Just like Express is unopinionated, how you apply the concept in the article is also unopinionated. And except I do not understand you clearly, it'd not make sense semantically to throw a success response as an error. Thanks for sharing your views.

Collapse
 
jorgee97 profile image
Jorge Gomez

Thanks for the article Orie, it really help me a lot.

Collapse
 
yuriytigiev profile image
YuriyTigiev

Why for a success case the last step is next(), but not a return res.status(200)?

Collapse
 
nedsoft profile image
Chinedu Orie

It's because, the function is a middleware, not a final destination, with next() returned, it would proceed to the next code to be executed.

Collapse
 
taniarascia profile image
Tania Rascia • Edited

This is great, thank you! Exactly what I needed.

Collapse
 
nedsoft profile image
Chinedu Orie

Glad to learn you found it helpful. ✌️

Collapse
 
maxiqboy profile image
Thinh Nguyen • Edited

Great Article, Thanks

btw, I got a small question in this own Error class :

class ErrorHandler extends Error {
  constructor(statusCode, message) {
    super();
    this.statusCode = statusCode;
    this.message = message;
  }
}
module.exports = {
  ErrorHandler
}
Enter fullscreen mode Exit fullscreen mode

Why we have to write like this (1)

super();
this.message = message;
Enter fullscreen mode Exit fullscreen mode

but not like this (2)

super(message);
Enter fullscreen mode Exit fullscreen mode

?

Actually, I wrote like (2) and then my own class lost a message property

It only comes back when I change it to (1).

What is the difference between them ?

Thanks,

Collapse
 
nedsoft profile image
Chinedu Orie • Edited

The super() method is used when a class (child) inherits from another class (parent).
The super() method provides a means of syncing the child's constructor to the parent's constructor.
Let me illustrate with an example

class Foor {
    constructor(name) {
        this.name = name
   }
    printName = () => {
      console.log(this.name);
   }
}

class Bar extends Foo {

     constructor(name) {
         super(name)
   }
}

const bar = new Bar('Test');

bar.printName() // Test
Enter fullscreen mode Exit fullscreen mode



Now, looking at the code above,
Foorequires anameto be passed to its constructor in order to function, whenBarinherited fromFoo, there's no way thenamecould be passed down toFooif not with thesuper()`

So, in relation to the snippet that you shared above, you are passing the message to the parent which in this case is Error, that way the child ErrorHandler has no access to message

I hope this helps.

Collapse
 
peacefulseeker profile image
Alexey Vorobyov

Is there a necessity in Bar constructor at all?
You just pass the same Test value to the parent class ultimately. In this case
there is no need to a constructor at all I assume and Eslint should also hint about it.

Collapse
 
hirengohil13 profile image
Hiren Gohil

ReferenceError: ErrorHandler is not defined

const { handleError, ErrorHandler } = require('./helpers/error')
Imported ErrorHandler but not used into Index file.

Collapse
 
hirengohil13 profile image
Hiren Gohil

Nice Article

Can we make common structure for Error & Success both ?

Collapse
 
omancoding profile image
OmanCoding

Thank you for the article, I learned something.

I have question, how does Express know that next(error), should be passed to the Error Handling middleware (which has 4 arguments)?

If there is a middleware that has (res, req, next) that was sat up before the Error Handling middleware, will it get first to handle the error?

Nasser

Collapse
 
leivermoreno profile image
Leiver Moreno • Edited

This happens because even if you do not configure a middleware for error handling, express does it internally for you, so when an error occurs, all subsequent middlewares are skipped until the error handling middleware. How does express know what this middleware is? Because it has four arguments, error, req, res, next.

Collapse
 
briantuju profile image
Brian • Edited

The best error handling mechanism I've seen so far. I have a question though.

I understand that i have to require this in the index file:

const { handleError } = require("path-to-code")

Will I have to include this code

const { ErrorHandler } = require("path-to-code");

in every file where I need to handle errors apart from the index file?

Collapse
 
nedsoft profile image
Chinedu Orie

It's like any other module, you will have to require/import it anywhere you need to use it. No specific exceptions, it depends on the use case.

Collapse
 
briantuju profile image
Brian

Thank you so much