Express.js Error Handler Not Catching Errors Fix
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-errorsfor 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
unhandledRejectionanduncaughtExceptionhandlers
Common Mistakes with error handling
- Using
headandtailinstead of pattern matching, causing runtime errors on empty lists - Forgetting that lazy evaluation defers computation until the value is forced, causing space leaks with unevaluated thunks
- Using
returnto 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
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro