Module 43: Backend Architecture
Error handling in one place
🏠The everyday picture
A complaints desk with clear rules: lost luggage goes here, refunds there, anything else to the manager. Staff don't each invent their own way of handling complaints.
📘What it really is
- Define error classes for expected problems:
export class AppError extends Error {
status: number;
constructor(status: number, message: string) {
super(message);
this.status = status;
}
}
export class NotFoundError extends AppError { constructor(m: string) { super(404, m); } }
export class ConflictError extends AppError { constructor(m: string) { super(409, m); } }
- One error-handling middleware, added last, with four parameters:
app.use((err, req, res, next) => {
if (err instanceof z.ZodError) {
return res.status(422).json({ error: "Validation failed", fields: z.flattenError(err).fieldErrors });
}
if (err instanceof AppError) {
return res.status(err.status).json({ error: err.message });
}
console.error(err); // Module 45: proper logging
res.status(500).json({ error: "Something went wrong. Please try again." });
});
- Expected errors (not found, invalid input, conflicts) get specific statuses and helpful messages.
-
Unexpected errors (bugs, a crashed disk) are logged in full on the server, and the client gets a generic 500 message, never details.
-
Express 5 sends errors thrown in
asynchandlers to this middleware automatically. - Never let the process crash silently: also log
unhandledRejectionanduncaughtException, then restart cleanly (your host or process manager restarts it, Level 6).
🖱️Try it
Add the error classes and the error handler. Remove try/catch from controllers where the handler now covers it.
Test: an unknown id (404), invalid input (422 with fields), closing a closed enquiry (409), and a deliberate bug
(500 with a generic message, full details in the terminal).
✅Quick check
A database error occurs. What does the client see, and what's logged?
Show the answer
The client sees a generic 500 message; the full error and stack are logged on the server.
Why handle errors in one middleware instead of in every route?
Show the answer
Consistent responses everywhere, less repeated code, and no route can forget.
