Проверяй данные на границе API и централизуй всю обработку ошибок в одной middleware с четырьмя параметрами.
Открыть этот урок в KodokonЛюбые входящие данные враждебны, пока не доказано обратное: тело JSON, параметры URL, заголовки. Валидации место на границе приложения - до контроллера - чтобы внутренние слои работали с гарантированными данными. Дальше различай два семейства ошибок: операционные (некорректный ввод, отсутствующий ресурс, конфликт), ожидаемые и переводимые в 4xx, и программные (баги), которые должны давать непрозрачный 500 на стороне клиента и полную трассировку стека на стороне сервера.
export class HttpError extends Error {
constructor(status, message, details = undefined) {
super(message);
this.status = status;
this.details = details;
}
}Middleware валидации перехватывает запрос до контроллера. Ручная версия ниже показывает принцип; в продакшене декларативная схема (zod стал стандартом) вдобавок даёт вывод типов и структурированные сообщения. Компромисс: одна зависимость и небольшая цена во время выполнения - в обмен на централизованные, компонуемые правила, о которых невозможно забыть.
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 распознаёт middleware ошибок по её сигнатуре с четырьмя параметрами (err, req, res, next). Зарегистрированная последней, после всех маршрутов, через app.use(errorHandler), она становится единственным выходом для ошибок API: однородный формат ответа, логирование в одном месте, никаких утечек внутренней информации. Контроллеры же просто вызывают 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);
}