Implementing Error Handling
1. Understanding Error Flow
Sync throw ──┐
Async w/await ──┤ Express 5
next(err) ──┼──▶ Error middleware (4-arg)
Promise reject ──┘ └─ res.status(...).json({...})
Express 4 caveat: async errors NOT auto-caught — wrap with try/catch or asyncHandler
Express 4 Express 5
Async rejections crash unless wrapped Auto-forwarded to error middleware EXPRESS 5
2. Creating Error Middleware
Example: Standard error handler
app. use (( err , req , res , next ) => {
if (res.headersSent) return next (err); // delegate to default
const status = err.status || err.statusCode || 500 ;
logger. error ({ err, id: req.id, status }, err.message);
res. status (status). json ({
error: { message: err.expose ? err.message : "Internal Server Error" , code: err.code }
});
});
3. Handling Synchronous Errors
Example: Sync throw auto-caught
app. get ( "/users/:id" , ( req , res ) => {
const id = Number (req.params.id);
if ( ! Number. isInteger (id)) throw new HttpError ( 400 , "Invalid id" );
res. json ({ id });
});
4. Handling Async Errors
Example: Express 5 native
// Express 5 — async errors auto-forwarded
app. get ( "/users/:id" , async ( req , res ) => {
const user = await db.users. findById (req.params.id);
if ( ! user) throw new HttpError ( 404 );
res. json (user);
});
Example: Express 4 — wrap with asyncHandler
const asyncHandler = ( fn ) => ( req , res , next ) =>
Promise . resolve ( fn (req, res, next)). catch (next);
app. get ( "/users/:id" , asyncHandler ( async ( req , res ) => { /* ... */ }));
5. Passing Errors to next()
Pattern Effect
next(err)Skip to error middleware
next() (no arg)Continue normally
throw err in syncAuto-caught by Express
throw in async (Express 5)Auto-forwarded
6. Creating Custom Error Classes
Example: HttpError hierarchy
export class HttpError extends Error {
constructor ( status , message = "" , code = null ) {
super (message);
this .name = this . constructor .name;
this .status = status;
this .code = code;
this .expose = status < 500 ; // safe to send to client
Error. captureStackTrace ?.( this , this . constructor );
}
}
export class NotFoundError extends HttpError { constructor ( m = "Not found" ) { super ( 404 , m); } }
export class ValidationError extends HttpError { constructor ( m = "Bad request" , details ) { super ( 400 , m); this .details = details; } }
export class UnauthorizedError extends HttpError { constructor ( m = "Unauthorized" ) { super ( 401 , m); } }
7. Setting Error Status Codes
Property Convention
err.statusExpress convention
err.statusCodehttp-errors / Boom
err.codeString code (e.g., USER_NOT_FOUND)
err.exposeSend message to client?
Example: Problem Details (RFC 7807)
app. use (( err , req , res , next ) => {
const status = err.status || 500 ;
res. status (status). type ( "application/problem+json" ). json ({
type: err.type || `https://errors.example.com/${ err . code || "internal"}` ,
title: err.expose ? err.message : "Internal Server Error" ,
status,
detail: err.detail,
instance: req.originalUrl,
requestId: req.id
});
});
9. Logging Errors
Library Notes
pinoFast structured JSON logger RECOMMENDED
winstonMulti-transport, mature
bunyanJSON logger (older)
Example: pino in error handler
logger. error ({
err: { message: err.message, stack: err.stack, code: err.code },
req: { id: req.id, method: req.method, url: req.originalUrl, userId: req.user?.id }
}, "Request failed" );
10. Handling 404 Not Found
Example: 404 + delegate
// Place AFTER all routes, BEFORE error handler
app. use (( req , res , next ) => next ( new HttpError ( 404 , `Not found: ${ req . method } ${ req . originalUrl }` )));
11. Differentiating Development vs Production Errors
Example: Hide stack in prod
app. use (( err , req , res , next ) => {
const status = err.status || 500 ;
const body = { error: err.expose ? err.message : "Internal Server Error" };
if (process.env. NODE_ENV !== "production" ) {
body.stack = err.stack;
body.detail = err;
}
res. status (status). json (body);
});
Environment Behavior
development Full stack trace in response
production Generic message, log full details
12. Using express-async-errors Package
Example: Express 4 patch
// app.js — first line
import "express-async-errors" ;
import express from "express" ;
// Now async route handlers forward rejections automatically
Note: Not needed in Express 5 — async error forwarding is built-in.