Error Handling
Catching and responding to errors properly - the error-handling middleware signature, async error patterns, and centralizing error responses.
What it is, in plain English: Error handling in Express centers on a special kind of middleware: a function with FOUR parameters (err, req, res, next), which Express recognizes as an error handler and skips for normal requests. Errors reach it either by calling next(err) explicitly, or (in Express 5) automatically when an async route handler throws or rejects. Centralizing error handling in one place keeps every route from repeating the same try/catch and response-formatting logic.
Centralizing how your app responds to errors
The error-handling middleware signature
// A middleware function with FOUR parameters is special -- Express
// recognises it as an error handler and routes errors to it:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: "Something went wrong" });
});
// This MUST be registered LAST, after all your routes:
app.get("/", homeHandler);
app.get("/users", usersHandler);
app.use((err, req, res, next) => { /* error handler */ }); // lastTriggering the error handler -- next(err)
app.get("/users/:id", (req, res, next) => {
const user = findUser(req.params.id);
if (!user) {
const err = new Error("User not found");
err.statusCode = 404;
return next(err); // passes the error to the error-handling middleware
}
res.json(user);
});
app.use((err, req, res, next) => {
res.status(err.statusCode ?? 500).json({ error: err.message });
});The 404 handler -- for routes that don't exist
// Place this AFTER all your real routes, BEFORE the error handler:
app.get("/", homeHandler);
app.get("/users", usersHandler);
// Catches anything that didn't match any route above:
app.use((req, res) => {
res.status(404).json({ error: "Route not found" });
});
app.use((err, req, res, next) => { // the actual error handler, last
res.status(500).json({ error: err.message });
});Async errors -- Express 4 vs Express 5
// Express 4 -- errors thrown in async functions are NOT caught automatically:
app.get("/users/:id", async (req, res, next) => {
try {
const user = await db.users.findById(req.params.id);
res.json(user);
} catch (err) {
next(err); // MUST manually catch and forward
}
});
// Express 5 -- catches them automatically, no try/catch needed:
app.get("/users/:id", async (req, res) => {
const user = await db.users.findById(req.params.id); // if this throws, Express 5 catches it
res.json(user);
});
Try It Yourself (Node.js logic)
Official Sources
- Express.js — Error Handling Guide
- Express.js 5 — Migration Guide
- Node.js Docs — Error Handling Best Practices
The Codex links only to official documentation. No third-party tutorials.