Node.js cookbook · Beginner
Centralize error handling in Express 5
Return consistent error responses and never leak stack traces, including for async handler failures.
Approach
Throw typed HttpErrors from handlers and services, let Express 5 route async rejections to a single error-handling middleware (4 arguments), log unexpected errors with request context, and return Problem Details JSON. Add a 404 handler before it.
Solution
import express, { type ErrorRequestHandler } from "express";
export class HttpError extends Error {
constructor(public status: number, public title: string, public detail?: string) { super(detail ?? title); }
}
export const notFound = (what: string) => new HttpError(404, "Not Found", `${what} not found`);
const app = express();
app.use(express.json({ limit: "100kb" }));
app.get("/api/orders/:id", async (req, res) => {
const order = await orders.find(req.params.id);
if (!order) throw notFound("Order"); // Express 5 catches async throws
res.json(order);
});
app.use((req, res) => {
res.status(404).type("application/problem+json").json({ title: "Not Found", status: 404 });
});
const errorHandler: ErrorRequestHandler = (err, req, res, _next) => {
if (err instanceof HttpError) {
return res.status(err.status).type("application/problem+json")
.json({ title: err.title, status: err.status, detail: err.detail });
}
if (err?.type === "entity.parse.failed") {
return res.status(400).type("application/problem+json").json({ title: "Malformed JSON", status: 400 });
}
req.log?.error({ err }, "unhandled error"); // pino-http logger
res.status(500).type("application/problem+json").json({ title: "Internal Server Error", status: 500 });
};
app.use(errorHandler);
Pitfalls
- Express 4: async errors aren't caught without wrappers (upgrade to Express 5 or wrap handlers).
- Sending
err.messagefor unexpected errors. - Error middleware with only 3 parameters (Express won't treat it as an error handler).
Variations
- Fastify's
setErrorHandlerwith schema-based validation errors.
Further reading: Express: Error handling
Go deeper: Express: The Middleware Stack and Error Handling in the Request Lifecycle course traces this step of the request, with a production bug and a runnable simulation.
Reference: https://expressjs.com/en/guide/error-handling.html
More Node.js recipes
- Add health and readiness endpoints to a Node.js service
- Add JWT authentication to an Express API
- Cache expensive lookups in Redis from Node.js
- Call external APIs with timeouts and retries in Node.js
- Hash passwords and implement login safely in Node.js
- Implement cursor pagination with PostgreSQL in Node.js
- Offload CPU-heavy work with worker threads
- Process background jobs with BullMQ