DEV Community

Adva Apelboim
Adva Apelboim

Posted on

Error Handling in Express.js

Topics:

  1. Before we start
  2. Motivation
  3. Let’s build a server!
  4. Error handling & middlewares
  5. The Error object
  6. Custom Errors
  7. Conclusion

1. Before we start

Error and exception handling is a topic that every developer, beginner or advanced, finds himself tackling almost all the time. There are many ways to avoid exceptions from crushing our server, but many of them aren’t letting the developer have full control of the structure and timing of the error thrown. In this tutorial, I will guide you through a simple, modular and maintainable way to handle errors and exceptions in Express server.

before we start, one important note – this tutorial is not for absolute beginners. You will need to have basic knowledge of Node.js, Express server, npm, and using Postman to send basic http requests. You will also need to have them installed on your local machine.

2. Motivation

Obviously, in a basic Node.js http server, when an exception is thrown without being handled, the server crushes and our service is down. No developer wants this. Also, sometimes we do catch the error with a try catch block, but it’s hard to know what is causing it, and if or how we can handle it. So, let’s make a basic server and then move on to better understanding errors.

3. Let’s build a server!

We build our Node.js server with Express. Express is a fast, unopinionated, minimalist web framework for Node.js.
So let’s initialize our project and then add Express.
npm init -y
npm install express

We’ll also need Nodemon to restart our server on every change.
npm i nodemon

And to set it up you we’ll need to add the following line to our scripts object inside package.json file.
“start”:”nodemon index.js”

Now, add this code to your index.js file:

// index.js
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;

app.get("/", (req, res) => {
  console.log("I'm a service being called");
  console.log("Now I am going to throw an error, because something went wrong");
  throw new Error();
});

app.listen(port, () => {
  console.log(`Server is listening on port ${port}...`);
});
Enter fullscreen mode Exit fullscreen mode

Our root route simply console.log the incoming requests.
To start the server:
npm start

4. Error handling & middlewares

What is a middleware in Node.js? Middleware comes in the middle of the request and response cycle of the Node.js execution. It also provides access to many functions like request and response objects with the Next function of the cycle. In this tutorial we will make an error middleware to handle our errors.

If we run this server and send a GET request to http://localhost:3000 the server will crash. It happens because the error is thrown but not handled by any function. let’s add another package called express-async-errors.
npm i express-async-errors

This package does two things:

  • wraps the function in a try/catch block under the hood, so we don’t need to do it explicitly every time.
  • In the virtual catch block, it calls next(error). The error is caught and passed to the next middleware.

So, to catch the next(error), let’s create an error handling middleware.
Create a new folder- middlewares, inside this folder create a new file called errorHandler.js. Then copy-paste these lines:

// middlewares/errorHandler.js
exports.errorHandler = (error, req, res, next) => {
  res.status(error.status || 500);
  res.json({ message: error.message || "Internal Server Error" });
};
Enter fullscreen mode Exit fullscreen mode

The first parameter is the error thrown, then the req and res objects, and next is the last one. The service catches the error and returns it as a response, or returns a default error message.
Now let’s import the handler on the top of the server file , and add it as an Express middleware. Notice that the handler is placed at the end of the server routes, so when next(error) is called, it will be caught by this error handler.
The updated server:

// index.js 
const express = require("express");
const { errorHandler } = require("./middlewares/errorHandler");
const app = express();
const port = process.env.PORT || 3000;

app.get("/", (req, res) => {
  console.log("oh no something went wrong 😵‍💫");
  console.log("I throw an error, because something went wrong");
  throw new Error();
});

app.use(errorHandler);

app.listen(port, () => {
  console.log(`Server is listening on port ${port}...`);
});
Enter fullscreen mode Exit fullscreen mode

If we run the server and send a request the server will not crush and you’ll get the following message on postman:

postman message
Notice the 500 status code:

postman status
We got the default error we set inside the error handler. Let’s continue and learn about the Error object, and return custom errors in the response.

5. The Error object

We can talk a lot about Node.js Error object, but let’s talk about the most common properties: name and message.
Here’s an example:

class Error {
  constructor(message) {
    this.message = message;
  }
}

const error = new Error("this is the message");
error.name = "errorName";

console.log(error.name); //errorName
console.log(error.message); //this is the message
Enter fullscreen mode Exit fullscreen mode

An error object can take a message, and it’s name can be set afterwards. This will be important when we’ll go on and create some Error objects of our own on the next step.

6.Custom errors

Let’s consider the 404 – Not Found status code. We might want it to appear on many occasions – when a property is missing from a request, or when we did not find an entity in the database. We will need to create an error for every use-case in which we expect an error in our code, so we will be able to determine where the error occurs, and what caused it.

Now let’s implement this. Create a new folder called “errors”. Inside it, create a file called NotFoundError.js. inside it, create a class called NotFound like so:

//errors/NotFoundError.js 
class NotFound extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
    this.status = 404;
  }
}
Enter fullscreen mode Exit fullscreen mode

The NotFound class inherits the Error class and adds some properties of its own:

  • Status – we wanted to consider the 404 status, so the class will have it as a property
  • Name – in this example, the name property will hold the name of the class.

But this is not enough, right? 404 status can occur in our code for many reasons that we probably want to tell apart from each other. This is where the magic happens. We can continue and create custom errors that inherits this basic NotFound class, and specify the exact reason the error has occurred, for example:

// errors/NotFoundError.js
class NotFound extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
    this.status = 404;
  }
}

class EntityNotFound extends NotFound {
  constructor(entity) {
    super(`${entity} not found...`);
    this.name = this.constructor.name;
    this.entity = entity;
  }
}

class PropertyNotFound extends NotFound {
  constructor(property) {
    super(`Property: ${property} not found...`);
    this.name = this.constructor.name;
    this.property = property;
  }
}

module.exports = { EntityNotFound, PropertyNotFound };
Enter fullscreen mode Exit fullscreen mode

Now we can use a custom error, with the same status code as the base error we made, in order to send a specific response. If the error is an entity we did not find, we can use the EntityNotFound class, pass it an entity name, and get specific information about the error.
Let’s extend our basic server and try this out:

// index.js 
const express = require("express");
const { errorHandler } = require("./middlewares/errorHandler");
const {
  EntityNotFound,
  PropertyNotFound,
} = require("./errors/NotFound.errors");
const app = express();
const port = process.env.PORT || 3000;

app.get("/", (req, res) => {
  console.log(
    "I'm a service trying to access the req.params.id from the request"
  );
  throw new PropertyNotFound("id");
});

app.get("/user", (req, res) => {
  console.log("I'm a service trying to find a user entity in the db");
  throw new EntityNotFound("user");
});

app.use(errorHandler);

app.listen(port, () => {
  console.log(`Server is listening on port ${port}...`);
});
Enter fullscreen mode Exit fullscreen mode

If you’ll run the server now and make the above calls, the server will still run, and you’ll get a custom message from each middleware.

7. Conclusion

Error handling is very important while you write your application, especially when you establish a Node.js server. Using an error handler and custom errors can improve your API response clarity, and make a more reliable and stable server, in a simple and elegant way.
what do you think? leave your comments 🙂

Top comments (2)

Collapse
 
yarden_ishshalom_b1edea7 profile image
Yarden Ish Shalom

Great tutorial! Thanks!

Collapse
 
ofirpeleg_ profile image
Ofir Peleg

Great job Adva⚡️