Skip to content

Express.js Error Handler Not Catching Errors Fix

DodaTech Updated 2026-06-24 3 min read

In this tutorial, you'll learn about Express.js Error Handler Not Catching Errors Fix. We cover key concepts, practical examples, and best practices.

The Problem

Errors thrown in Express route handlers are not caught by the error handler. The server crashes with UnhandledPromiseRejection or the client gets a generic HTML error page instead of a JSON response.

Quick Fix

Step 1: Define the error handler with 4 parameters

// Wrong — 3 parameters, not an error handler
app.use((req, res, next) => {
    res.status(500).json({ error: 'Server error' });
});

// Right — 4 parameters
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).json({ error: 'Internal server error' });
});

Expected output: The error handler catches errors passed via next(err).

Step 2: Wrap async route handlers

// Wrong — async error not caught
app.get('/api/data', async (req, res) => {
    const data = await fetchData(); // throws
    res.json(data);
});

// Right — wrap with error handling
function asyncHandler(fn) {
    return (req, res, next) => {
        Promise.resolve(fn(req, res, next)).catch(next);
    };
}

app.get('/api/data', asyncHandler(async (req, res) => {
    const data = await fetchData();
    res.json(data);
}));

Expected output: Async errors are forwarded to the error handler.

Step 3: Use express-async-errors package

npm install express-async-errors
require('express-async-errors'); // import at the top
const express = require('express');

// Now async errors are caught automatically
app.get('/api/data', async (req, res) => {
    const data = await fetchData();
    res.json(data);
});

Expected output: No wrapper needed — async errors are caught automatically.

Step 4: Call next(err) for synchronous errors

app.get('/api/data', (req, res, next) => {
    try {
        const data = getData();
        res.json(data);
    } catch (err) {
        // Wrong — error not forwarded
        res.status(500).json({ error: err.message });
        // Right — forward to error handler
        next(err);
    }
});

Expected output: The error is handled by the centralized error handler.

Step 5: Handle uncaught promise rejections globally

process.on('unhandledRejection', (reason, promise) => {
    console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});

process.on('uncaughtException', (error) => {
    console.error('Uncaught Exception:', error);
    process.exit(1); // exit with error for process manager to restart
});

Expected output: Unhandled promise rejections are logged and do not crash silently.

Step 6: Place error handler after all routes

// Routes
app.use('/api', apiRoutes);
app.use('/', webRoutes);

// Error handler must be last
app.use((err, req, res, next) => {
    res.status(err.status || 500).json({
        error: err.message || 'Internal server error',
    });
});

Expected output: All errors from any route or middleware are caught.

Prevention

  • Use <a href="/backend/nodejs/">express</a>-async-errors for automatic async error handling
  • Define a 4-parameter error handler at the end of the middleware chain
  • Call next(err) instead of handling errors inline
  • Add global unhandledRejection and uncaughtException handlers

Common Mistakes with error handling

  1. Using head and tail instead of pattern matching, causing runtime errors on empty lists
  2. Forgetting that lazy evaluation defers computation until the value is forced, causing space leaks with unevaluated thunks
  3. Using return to exit a function early instead of wrapping a pure value in the monad

These mistakes appear frequently in real-world EXPRESS code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### Why does my error handler not catch errors from async routes?

Async functions return promises. If the promise rejects, Express does not catch it unless you call next(err) in a .catch() or use <a href="/backend/nodejs/">express</a>-async-errors. Wrap async handlers with a catch that calls next(err).

What is the difference between app.use and app.get for error handlers?

Only app.use with 4 parameters works as an error handler. The four parameters (err, req, res, next) tell Express this is an error-handling middleware. app.get with 4 parameters does not work.

Should I exit the process on uncaughtException?

Yes. After an uncaught exception, the application may be in an inconsistent state. Log the error, exit with code 1, and let a process manager (PM2, Docker) restart the application. This prevents serving corrupted data.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro