DEV Community

Srishti Prasad
Srishti Prasad

Posted on

Error Handling with Express

INTRODUCTION

Error handling in Express refers to the process of catching and managing errors that occur during the handling of HTTP requests. In an Express application, errors can arise from various sources such as synchronous code, asynchronous operations, database queries, file I/O operations, or external API calls. Proper error handling ensures that your application remains stable and provides appropriate responses to clients when errors occur.

Before diving into error handling in Node.js, it's important to grasp the concept of errors within the platform. Generally, errors in Node.js are categorized into two main types:

  • operational errors
  • programming errors

OPERATIONAL ERRORS - problems that we can't predict or user mistaken entered wrong values.They have nothing to do with bugs in our code. Instead, they depend on the user, or the system, or the network, but developers need to handle them thoughtfully.So, things like a user accessing an invalid route, inputting invalid data, or an application failing to connect to the database.

PROGRAMMING ERRORS - bugs that we developers introduce into our code. For example, trying to read properties from an undefined variable, using await without async, or many other errors, that we might make.

In order to understand where to handle error , first we need to understand potential error points.So identify areas in your code where errors might occur. It could be :

  • Database queries
  • File I/O operations
  • Network requests
  • External API calls
  • Business logic that may encounter unexpected conditions

Handling error in synchronous code

We typically use try-catch blocks to catch and handle errors that may occur during execution.

The try block contains the synchronous code that may potentially throw an error.
If an error occurs within the try block, it is caught by the corresponding catch block.
Inside the catch block, you can handle the error by logging it, sending an error response, or taking any other necessary action.

function divide(a, b) {
  if (b === 0) {
    throw new Error('Division by zero is not allowed');
  }
  return a / b;
}

try {
  const result = divide(10, 0);
  console.log('Result:', result);
} catch (error) {
  console.error('An error occurred:', error.message);
}
Enter fullscreen mode Exit fullscreen mode

In this example, the divide function throws an error if the second argument (b) is zero. The error is caught by the try-catch block, and the appropriate error message is logged to the console.

Handling error in Asynchronous code

Asynchronous error handling in JavaScript refers to the process of managing errors that occur in code that executes asynchronously, such as operations involving callbacks, Promises, or async/await syntax.

Errors Thrown Within Async Functions:

  • When you have an asynchronous function (declared with async), it can contain code that may cause errors, just like regular functions.
  • You can use try-catch blocks inside async functions to catch these errors and handle them locally within the function.
  • It's like putting a safety net around the risky parts of your code to prevent them from causing your entire program to crash.
async function someAsyncFunction() {
  try {
    // Asynchronous code that may throw an error
    await someAsyncOperation();
  } catch (error) {
    // Handle the error locally
    console.error('An error occurred:', error);
  }
}
Enter fullscreen mode Exit fullscreen mode

Passing Errors to Next Function (Express.js Context):

  • In web frameworks like Express.js, when handling HTTP requests, errors might occur during the processing of these requests.
  • If an error occurs in a route handler or middleware, you can use a special function called next() to pass the error to the next piece of code in line to handle it. Express.js will then skip any remaining route handlers and middleware functions and look for a special error handling middleware function to handle the error.
  • This allows you to centralize your error handling logic, making it easier to manage and maintain across your application.

In simple terms, async functions let you catch and handle errors inside the function itself, while next() in Express.js lets you pass errors along to be handled elsewhere in your application. Both techniques help ensure your code can gracefully recover from errors and continue running smoothly.

// Middleware function
app.use((req, res, next) => {
  console.log('Middleware 1');
  next(); // Calling next without any argument, continues to the next middleware or route handler
});


// Route handler
app.get('/', (req, res, next) => {
  console.log('Route handler');
  next(); // Calling next without any argument, continues to the next middleware or route handler
});


// Middleware function
app.use((req, res, next) => {
  console.log('Middleware 2');
  // Passing an error to next function
  next(new Error('Custom error message')); // Express treats this as an error
});
// Error handling middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something went wrong!');
});

Enter fullscreen mode Exit fullscreen mode
  1. Request Flow: When a request is made to the root path ("/"), Express processes it through middleware and route handlers.
  2. Execution Order: Middleware 1 runs first, followed by the route handler, and then Middleware 2.
  3. Error Trigger*: In Middleware 2, an error is intentionally triggered by calling next(error).
  4. Error Handling: Since an error is passed to next(), Express treats the request as an error and jumps directly to the error handling middleware.
  5. Error Response: Inside the error handling middleware, the error is caught, and a 500 Internal Server Error response is sent to the client along with a custom error message

Let's delve deeper into error handling middleware

When to Use Error Handling Middleware:

  1. Centralized Error Handling: Error handling middleware is suitable for handling errors that occur across multiple routes and middleware functions. Instead of duplicating error handling logic in each route handler, you can centralize error handling in one place.
  2. Asynchronous Error Handling: Error handling middleware can handle errors from asynchronous operations, including Promises and async/await functions.
  3. Global Error Handling: Error handling middleware catches unhandled errors that occur during the request-response cycle, ensuring that no error goes uncaught.

How to Use Error Handling Middleware:

  • Error handling middleware functions have four parameters: (err, req, res, next).
  • They are defined using the app.use() method with an additional error parameter (conventionally named err) to catch errors passed by next().
  • Inside the error handling middleware, you can handle the error as needed, log it for debugging purposes, and send an appropriate error response to the client.
  • Error handling middleware provide a centralized location for handling errors across your application, meaning having a single, dedicated mechanism or set of functions responsible for catching and managing errors that occur during the execution of your application.

here is coded example to understand error handling in asynchronous code:

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

// Synchronous route handler with try-catch block
app.get('/sync', (req, res) => {
  try {
    // Simulate a synchronous operation that might throw an error
    throw new Error('Synchronous Error');
  } catch (err) {
    console.error(err);
    res.status(500).send('Internal Server Error');
  }
});

// Asynchronous route handler using async/await
app.get('/async', async (req, res) => {
  try {
    // Simulate an asynchronous operation that might throw an error
    await new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(new Error('Asynchronous Error'));
      }, 1000);
    });
  } catch (err) {
    console.error(err);
    res.status(500).send('Internal Server Error');
  }
});

// Error handling middleware to catch all errors
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).send('Internal Server Error');
});

// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

Enter fullscreen mode Exit fullscreen mode

When the server responds with a 500 status code and the message 'Internal Server Error', it indicates to the client that an unexpected error occurred during the processing of the request, providing a generic message to the client while also logging the specific error message ('Synchronous Error') for debugging purposes.

NOTE: new Error("this is the error message")
here we can create new Error object, we can provide a custom message as an argument to the Error constructor.
the provided message "this is the error message" becomes the value of the message property of the resulting Error object.

Hope you get all the concepts very well๐Ÿ‘
Please do like the blog if you found it helpful โค๏ธ
Do comment and let me know how did you like the blog.
If struck feel free to reach out to me and post you doubt in comment section, I'll try my best to answer them.

Top comments (2)

Collapse
 
naghikhanimahmoud profile image
Mahmoud Naghikhani

Thank you.
The content was very complete and practical despite its simplicity

Collapse
 
srishtikprasad profile image
Srishti Prasad

Thanks for your appreciation!