Kodokon kodokon.com

Валидация входных данных и middleware ошибок

Проверяй данные на границе API и централизуй всю обработку ошибок в одной middleware с четырьмя параметрами.

9 мин · 3 вопросов

Открыть этот урок в Kodokon

Любые входящие данные враждебны, пока не доказано обратное: тело JSON, параметры URL, заголовки. Валидации место на границе приложения - до контроллера - чтобы внутренние слои работали с гарантированными данными. Дальше различай два семейства ошибок: операционные (некорректный ввод, отсутствующий ресурс, конфликт), ожидаемые и переводимые в 4xx, и программные (баги), которые должны давать непрозрачный 500 на стороне клиента и полную трассировку стека на стороне сервера.

JAVASCRIPT
export class HttpError extends Error {
  constructor(status, message, details = undefined) {
    super(message);
    this.status = status;
    this.details = details;
  }
}
Ошибка, которая несёт свой HTTP-статус: базовый кирпичик.

Middleware валидации перехватывает запрос до контроллера. Ручная версия ниже показывает принцип; в продакшене декларативная схема (zod стал стандартом) вдобавок даёт вывод типов и структурированные сообщения. Компромисс: одна зависимость и небольшая цена во время выполнения - в обмен на централизованные, компонуемые правила, о которых невозможно забыть.

JAVASCRIPT
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();
}
Подключи её так: router.post('/', validateUser, controller.create).

Express распознаёт middleware ошибок по её сигнатуре с четырьмя параметрами (err, req, res, next). Зарегистрированная последней, после всех маршрутов, через app.use(errorHandler), она становится единственным выходом для ошибок API: однородный формат ответа, логирование в одном месте, никаких утечек внутренней информации. Контроллеры же просто вызывают next(err).

JAVASCRIPT
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);
}
4xx прозрачны для клиента, 500 скрыты, но записаны в лог.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Как Express отличает middleware ошибок от обычной?
    • По имени, в котором должно быть слово error
    • По сигнатуре с четырьмя параметрами (err, req, res, next)
    • По позиции в самом начале цепочки
    • По явному вызову app.setErrorHandler
  2. Какой HTTP-ответ правильный для неожиданной программной ошибки?
    • 500 с сообщением и стеком, чтобы помочь диагностике
    • 400, чтобы обозначить, что запрос не удался
    • 500 с обобщённым сообщением, а полная ошибка логируется на стороне сервера
  3. Что происходит в Express 4, если async-обработчик отклоняется без try/catch?
    • Ошибка всё равно доходит до middleware ошибок
    • Запрос висит: отклонение никогда не передаётся в next
    • Express автоматически возвращает 400
    • Процесс Node немедленно останавливается