Four Arguments — Express's Special Signature
Error Middleware
Error middleware has a 4-arg signature. Mount last. Centralize all error responses here.
What you'll learn
- Recognize the (err, req, res, next) signature
- Trigger error middleware
- Build a typed error class
Express recognizes a middleware as an error handler by its signature: it takes four arguments instead of three.
The Signature
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: "internal server error" });
}); That extra first parameter (err) is the trigger.
Trigger It
Three ways to send an error to this middleware:
1. Call next(err)
app.get("/users/:id", (req, res, next) => {
if (!validId(req.params.id)) {
return next(new Error("bad id"));
}
res.json(loadUser(req.params.id));
}); 2. Throw (Express 5 only)
app.get("/users/:id", (req, res) => {
if (!validId(req.params.id)) {
throw new Error("bad id"); // Express 5 catches sync AND async throws
}
res.json(loadUser(req.params.id));
}); In Express 4, async throws don’t get caught — see the next lesson.
3. Async Rejection (Express 5)
app.get("/users/:id", async (req, res) => {
const user = await db.users.findById(req.params.id);
// if findById rejects, Express 5 forwards to the error middleware
res.json(user);
}); A Typed Error Class
export class HttpError extends Error {
constructor(status, code, message) {
super(message);
this.status = status;
this.code = code;
}
} Now your error middleware can branch on type:
app.use((err, req, res, next) => {
if (err instanceof HttpError) {
return res.status(err.status).json({
error: { code: err.code, message: err.message },
});
}
// unexpected — log details, generic response
console.error("unexpected error", err);
res.status(500).json({
error: { code: "internal", message: "internal server error" },
});
}); Order
Mount error middleware after every route:
// routes
app.use("/api", apiRouter);
// 404
app.use((req, res) => {
res.status(404).json({ error: "not found" });
});
// errors (must be last)
app.use(errorHandler); If you mount the error handler too early, requests skip past it because they haven’t errored yet.
Don’t Leak Stacks to Clients
Send a generic message. Log the stack server-side:
app.use((err, req, res, next) => {
console.error(err); // full stack in your logs
res.status(500).json({
error: { message: "internal server error" },
});
}); In development, you might include err.stack in the response for
convenience. Never in production.