Validate data at the edge of the API and centralize all error handling in a single four-parameter middleware.
Open this lesson in KodokonEvery incoming piece of data is hostile until proven otherwise: JSON body, URL parameters, headers. Validation belongs at the edge of the application - before the controller - so that inner layers work on guaranteed data. Then distinguish two families of errors: operational errors (invalid input, missing resource, conflict), expected and translated into 4xx, and programming errors (bugs), which must produce an opaque 500 on the client side and a full stack trace on the server side.
export class HttpError extends Error {
constructor(status, message, details = undefined) {
super(message);
this.status = status;
this.details = details;
}
}A validation middleware intercepts the request before the controller. The manual version below shows the principle; in production, a declarative schema (zod has become the standard) additionally provides type inference and structured messages. The trade-off: one dependency and a slight runtime cost, in exchange for centralized, composable rules that are impossible to forget.
import { HttpError } from './http-error.js';
export function validateUser(req, res, next) {
const { email, name } = req.body ?? {};
const errors = [];
if (typeof email !== 'string' || !email.includes('@')) {
errors.push('email: invalid format');
}
if (typeof name !== 'string' || name.trim().length < 2) {
errors.push('name: 2 characters minimum');
}
if (errors.length > 0) {
return next(new HttpError(400, 'Invalid body', errors));
}
next();
}Express recognizes an error middleware by its four-parameter signature (err, req, res, next). Registered last, after all routes with app.use(errorHandler), it becomes the API's single error exit: a homogeneous response format, logging in one place, no leaking of internal information. The controllers, for their part, simply call next(err).
export function errorHandler(err, req, res, next) {
if (res.headersSent) {
return next(err);
}
const status = err.status ?? 500;
const body = { error: err.message };
if (err.details) {
body.details = err.details;
}
if (status >= 500) {
console.error(err);
body.error = 'Internal server error';
}
res.status(status).json(body);
}