Concept
Express Error-Handling Middleware
In Express, error-handling middleware functions are defined with four arguments instead of three: (err, req, res, next). This signature tells Express to treat the function as an error catcher:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
success: false,
error: { message: err.message || 'Internal Server Error' }
});
});Whenever a normal middleware or route calls next(error), Express skips all remaining normal routing and jumps straight to this error handler.
Handling Asynchronous Errors
Express 4 does not automatically catch errors thrown inside asynchronous code (promises or callback hooks). If an async operation rejects, you must pass the error to next() manually, or the server will trigger an unhandledRejection and crash:
// ❌ WRONG: Exception inside db query will crash the server
app.get('/users', async (req, res) => {
const users = await db.find(); // If this fails, error is uncaught!
res.json(users);
});
// CORRECT: Explicit catch blocks passing to next()
app.get('/users', async (req, res, next) => {
try {
const users = await db.find();
res.json(users);
} catch (error) {
next
(Note: Express 5, currently active, resolves this by catching rejected promises in handlers automatically, but explicit wrappers or try/catch blocks are still standard for robust error shaping).
Structured Logging
In production, raw console.log statements do not scale because they output unstructured strings that are hard for search tools (like Kibana or Datadog) to parse. Use structured JSON loggers like Pino or Winston:
{"level":50,"time":1789988220000,"msg":"Database query failed","err":{"message":"Connection timeout","stack":"..."}}Common Mistakes
1. Declaring error-handling middleware with only three arguments
If you write app.use((err, req, res) => { ... }), Express interprets the function as a normal middleware rather than an error handler. It will execute it on normal requests, leading to application crashes.
2. Positioning the error handler before routes
Express matches middleware in order. If you place the error handler before routes (app.use(errorHandler) followed by app.get('/users')), it will not catch errors from those routes. Always register the error handler at the very bottom of the middleware chain.
Best Practices
- Register at the Bottom: Ensure your error-handling middleware is registered last, after all other routes and middleware declarations.
- Write an Async Wrapper: Use a utility function to automatically catch async errors and pass them to next, avoiding boilerplates:
const asyncHandler = fn => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; app.get('/users', asyncHandler(async (req, res) => { ... })); - : In production, do not send the raw trace back to the client. This exposes database credentials, routes, and internal structures to attackers.
