Good Code
The good version centralizes error mapping, checks headersSent, and keeps unexpected error output generic.
Lesson 06
Use one Express error handler to map application errors into HTTP responses.
import type { ErrorRequestHandler } from "express";
export const errorHandler: ErrorRequestHandler = (error, _req, res, next) => {
// Never write a second response after headers were sent.
if (res.headersSent) {
next(error);
return;
}
if (error.name === "ValidationError") {
res.status(400).json({ error: error.message });
return;
}
console.error({ error }, "Unhandled request error");
res.status(500).json({ error: "Internal server error." });
};router.post("/", async (req, res) => {
try {
const review = await service.createReview(req.body);
res.json(review);
} catch (error) {
// Each route invents its own error shape and status code.
console.error(error);
res.status(500).send("Something broke");
}
});
router.get("/:id", async (req, res) => {
try {
res.json(await service.getReview(req.params.id));
} catch {
res.status(400).json({ message: "Bad request" });
}
});The good version centralizes error mapping, checks headersSent, and keeps unexpected error output generic.
The bad version repeats catch blocks in routes and returns different shapes and status codes for similar failures.