How to Handle Errors in Express

How to Handle Errors in Express Introduction Express.js is a popular web application framework for Node.js, widely used for building robust APIs and web servers. Handling errors effectively in Express is crucial to maintain application stability, provide meaningful feedback to users, and simplify debugging during development. Without proper error handling, applications can crash unexpectedly, disp

Nov 17, 2025 - 11:04
Nov 17, 2025 - 11:04
 0

How to Handle Errors in Express

Introduction

Express.js is a popular web application framework for Node.js, widely used for building robust APIs and web servers. Handling errors effectively in Express is crucial to maintain application stability, provide meaningful feedback to users, and simplify debugging during development. Without proper error handling, applications can crash unexpectedly, display obscure error messages, or expose sensitive information, all of which harm user experience and security.

This tutorial provides a comprehensive guide on how to handle errors in Express. It covers practical techniques, best practices, useful tools, and real-world examples to help developers build resilient Express applications.

Step-by-Step Guide

1. Understanding Error Handling in Express

Express middleware functions have a specific signature that enables error handling. An error-handling middleware function must have four arguments: err, req, res, and next. Express distinguishes error handlers by this signature, routing errors to these functions automatically.

2. Creating a Basic Error-Handling Middleware

To handle errors globally, define an error-handling middleware at the end of your middleware stack:

Example:

app.use(function (err, req, res, next) {

  console.error(err.stack);

  res.status(500).send('Something broke!');

});

This middleware logs the error stack trace and sends a generic 500 Internal Server Error response.

3. Handling Synchronous Errors

Errors thrown in synchronous route handlers or middleware are automatically caught by Express and passed to the error-handling middleware.

Example:

app.get('/sync-error', (req, res) => {

  throw new Error('Synchronous error occurred');

});

4. Handling Asynchronous Errors

Errors inside asynchronous code such as promises or async/await must be forwarded explicitly to Express using next(err). Otherwise, Express won’t catch them automatically.

Example with Promises:

app.get('/async-error', (req, res, next) => {

  Promise.reject(new Error('Async error')).catch(next);

});

Example with async/await:

app.get('/async-await-error', async (req, res, next) => {

  try {

    await someAsyncOperation();

    res.send('Success');

  } catch (err) {

    next(err);

  }

});

5. Creating Custom Error Classes

To handle different types of errors more effectively, create custom error classes extending the built-in Error object. This allows you to add custom properties like status codes or error types.

Example:

class HttpError extends Error {

  constructor(statusCode, message) {

    super(message);

    this.statusCode = statusCode;

    this.name = this.constructor.name;

  }

}

You can then throw these errors in your routes:

app.get('/not-found', (req, res, next) => {

  next(new HttpError(404, 'Resource not found'));

});

6. Sending Proper HTTP Status Codes and Responses

In your error-handling middleware, check for the error’s status code and send appropriate responses to clients. Avoid exposing stack traces or internal details in production environments.

Example:

app.use((err, req, res, next) => {

  const status = err.statusCode || 500;

  const message = status === 500 ? 'Internal Server Error' : err.message;

  res.status(status).json({ error: message });

});

7. Handling 404 Errors

To handle requests that do not match any route, add a catch-all middleware before the error handler:

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

  const err = new HttpError(404, 'Not Found');

  next(err);

});

8. Logging Errors

Use logging libraries like winston or morgan to capture error details for debugging and monitoring. Log errors in your error middleware before sending responses.

Best Practices

1. Centralize Error Handling

Maintain a single error-handling middleware to simplify maintenance and ensure consistent responses across your application.

2. Avoid Leaking Sensitive Information

Never send stack traces or sensitive server details in responses to clients, especially in production. Use environment checks to conditionally include detailed error info.

3. Use Meaningful HTTP Status Codes

Return appropriate HTTP status codes (e.g., 400 for bad requests, 401 for unauthorized, 404 for not found, 500 for server errors) to help clients understand the error.

4. Handle Errors at the Right Level

Catch and handle errors as close to their source as possible, but allow unhandled errors to bubble up to the centralized handler.

5. Validate Inputs Rigorously

Prevent errors by validating user inputs and request data early using libraries like Joi or express-validator.

6. Use Async Error Wrappers

Wrap asynchronous route handlers with helper functions to automatically forward errors to Express. This reduces boilerplate try/catch blocks.

Example:

const asyncHandler = fn => (req, res, next) => {

  Promise.resolve(fn(req, res, next)).catch(next);

};

7. Monitor and Alert on Errors

Integrate error monitoring services such as Sentry or LogRocket to track runtime errors and get alerts for critical issues.

Tools and Resources

1. Express Documentation

The official Express documentation offers detailed guidance on middleware and error handling: Express Error Handling Guide

2. Logging Libraries

  • Winston: A versatile logging library for Node.js. GitHub
  • Morgan: HTTP request logger middleware for Express. GitHub

3. Validation Libraries

  • Joi: Powerful schema description and validation library. GitHub
  • Express-validator: Express middleware for validation. Official Site

4. Error Monitoring Tools

  • Sentry: Real-time error tracking for applications. Website
  • LogRocket: Frontend and backend error monitoring. Website

Real Examples

Example 1: Basic Error Handling Middleware

const express = require('express');

const app = express();

app.get('/', (req, res) => {

  res.send('Hello World!');

});

app.get('/error', (req, res) => {

  throw new Error('This is a forced error');

});

// 404 handler

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

  const err = new Error('Not Found');

  err.status = 404;

  next(err);

});

// Error handler

app.use((err, req, res, next) => {

  res.status(err.status || 500);

  res.json({

    message: err.message,

    error: req.app.get('env') === 'development' ? err : {}

  });

});

app.listen(3000, () => console.log('Server running on port 3000'));

Example 2: Async Error Wrapper

const asyncHandler = fn => (req, res, next) => {

  Promise.resolve(fn(req, res, next)).catch(next);

};

app.get('/async', asyncHandler(async (req, res) => {

  const data = await someAsyncFunction();

  res.json(data);

}));

FAQs

Q1: What happens if I don’t use error-handling middleware in Express?

Without an error handler, Express will crash the server or send default error messages that may expose sensitive information. Proper middleware ensures graceful error responses and prevents server crashes.

Q2: How do I differentiate between operational and programmer errors?

Operational errors are expected runtime issues like invalid inputs or database connection failures, which you can handle gracefully. Programmer errors are bugs in code like undefined variables, which should be fixed during development.

Q3: Can I handle errors per route instead of globally?

Yes, you can handle errors within individual route handlers using try/catch blocks or by passing errors to next(). However, global error-handling middleware simplifies maintenance.

Q4: How do I test error handling in Express?

Use testing frameworks like Mocha or Jest along with supertest to simulate requests that trigger errors and assert correct responses and status codes.

Q5: Is it safe to show stack traces in error responses?

Stack traces should only be shown in development environments. In production, avoid revealing stack traces to prevent exposing internal server details.

Conclusion

Effective error handling in Express is essential for building reliable, secure, and user-friendly web applications. By implementing centralized error-handling middleware, managing synchronous and asynchronous errors properly, and following best practices, developers can ensure smooth operation and easier debugging. Leveraging custom error classes, robust validation, and modern monitoring tools further enhances error management. Use the techniques and examples provided in this tutorial to elevate your Express applications’ resilience and maintainability.