Global Error Handling
Overview
If a database query throws an error inside an Express route, and you don't catch it, the Express server might crash, or worse, the user's request will hang forever, exposing raw SQL stack traces to the frontend. Global Error Handling is an architectural pattern that funnels EVERY single error in the application into one centralized middleware. This ensures that errors are uniformly logged, formatted perfectly for the frontend, and safely isolated.
Syntax
const express = require('express');
const app = express();
app.get('/api/broken', (req, res, next) => {
const err = new Error("Database connection failed!");
err.statusCode = 500;
// By passing the error into next(), Express INSTANTLY aborts
// the normal pipeline and jumps straight to the Error Middleware!
next(err);
});
// --- THE GLOBAL ERROR HANDLER ---
// Express knows this is the Error Handler specifically because
// it has exactly FOUR arguments (err, req, res, next)!
app.use((err, req, res, next) => {
console.error("CRITICAL ERROR LOG:", err.message);
const status = err.statusCode || 500;
// Always send a uniform, safe response to the client
res.status(status).json({
success: false,
error: status === 500 ? 'Internal Server Error' : err.message
});
});Common Pitfalls
- Forgetting the
nextargument in the Error Middleware. Even if you don't use it, you MUST define the function as(err, req, res, next). If you define it as(err, req, res), Express treats it as a standard route middleware, completely breaking the error interception pipeline. - Unhandled Promise Rejections in Async Routes. Express V4 does NOT natively catch errors thrown inside
asyncfunctions! If anawait db.query()fails, it bypasses Express entirely and crashes Node. You must wrap every async route in atry/catchand callnext(error), or use a wrapper likeexpress-async-errors.
Interview Questions
Security. A 500 error usually means a low-level crash (like a SQL syntax error or a Null Pointer). If you pass err.message directly to the client, you might accidentally send them your database credentials or exact table names in the stack trace, aiding hackers. Always mask 500 errors in production.
Real-World Example
A bulletproof async wrapper that automatically funnels rejected Promises into the Global Error Handler, eliminating the need to write try/catch in every single route.
// The Wrapper Function
const catchAsync = (fn) => {
// Returns a standard Express middleware signature
return (req, res, next) => {
// Executes the route. If it rejects, push it to next(err) instantly!
fn(req, res, next).catch(next);
};
};
// Usage: Perfectly clean async routes! No try/catch needed!
app.get('/api/users', catchAsync(async (req, res) => {
const users = await db.query('SELECT * FROM users');
res.json(users);
}));Check Your Knowledge
Test your understanding of Global Error Handling with these quick questions.