SyntaxStudy
Sign Up
Express.js Centralised Error Handling Middleware
Express.js Beginner 1 min read

Centralised Error Handling Middleware

Express's four-argument error handler `(err, req, res, next)` is the single place where all errors should be formatted and sent. Every route handler and middleware can pass an error to it by calling `next(err)`. In async routes, errors thrown inside `async` functions must be caught and forwarded with `next(err)` because Express cannot automatically catch promise rejections in Express 4. Creating custom error classes that extend `Error` lets you attach HTTP status codes and machine-readable error codes directly to the error object. The error handler reads these properties to produce a consistent response body. For unexpected errors, mask the details in production responses but log the full stack trace to your logging infrastructure. Express 5 (currently in beta) automatically catches errors in async route handlers, eliminating the need for try/catch wrappers. In Express 4, a small `asyncHandler` wrapper function that catches promise rejections and calls `next(err)` provides the same convenience.
Example
// middleware/errors.js
class AppError extends Error {
    constructor(message, status = 500, code = 'INTERNAL_ERROR') {
        super(message);
        this.status = status;
        this.code   = code;
        this.isOperational = true;
    }
}

// Wrap async route handlers to forward rejections
function asyncHandler(fn) {
    return (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
}

// Central error-handling middleware (register LAST)
function errorHandler(err, req, res, next) {
    const status = err.status || 500;
    const isProd = process.env.NODE_ENV === 'production';

    // Always log the full error
    console.error(`[${req.method} ${req.path}]`, err);

    res.status(status).json({
        error: {
            message: isProd && status === 500 ? 'Internal server error' : err.message,
            code:    err.code || 'INTERNAL_ERROR',
            ...(isProd ? {} : { stack: err.stack }),
        },
    });
}

// Usage in a route
const express = require('express');
const app     = express();
app.use(express.json());

app.get('/users/:id', asyncHandler(async (req, res) => {
    if (req.params.id === '0') throw new AppError('User not found', 404, 'NOT_FOUND');
    res.json({ id: req.params.id });
}));

app.use(errorHandler); // must be last
app.listen(3000);

module.exports = { AppError, asyncHandler, errorHandler };