Celebrate middleware

Архитектурная роль middleware валидации

В экосистеме серверных приложений на Node.js middleware выступает промежуточным слоем между входящим HTTP-запросом и бизнес-логикой. Основная задача — обеспечить предсказуемость входных данных до того, как они попадут в обработчики маршрутов.

Валидация через middleware позволяет отделить проверку структуры данных от основной логики приложения, снижая связность компонентов и повышая тестируемость.

Celebrate представляет собой специализированный middleware-слой, построенный на базе схем валидации Joi и предназначенный для интеграции с Express.


Модель работы Celebrate

Celebrate реализует принцип декларативной валидации: структура запроса описывается схемами, которые применяются к конкретным частям HTTP-запроса.

Обрабатываемые области запроса:

  • body — тело запроса
  • query — параметры строки запроса
  • params — маршрутные параметры
  • headers — HTTP-заголовки
  • cookies — cookies (при соответствующей настройке)

Каждая область может иметь собственную схему валидации, что позволяет изолировать правила проверки.


Базовая интеграция в Express

Типовой сценарий подключения middleware:

const express = require('express');
const { celebrate, Joi, Segments } = require('celebrate');

const app = express();
app.use(express.json());

app.post(
  '/users',
  celebrate({
    [Segments.BODY]: Joi.object().keys({
      username: Joi.string().alphanum().min(3).max(30).required(),
      email: Joi.string().email().required(),
      age: Joi.number().integer().min(0)
    })
  }),
  (req, res) => {
    res.json({ status: 'ok' });
  }
);

В данном примере:

  • схема описывает структуру body
  • запрос отклоняется до попадания в обработчик при несоответствии
  • обработчик получает только валидированные данные

Segments как механизм разделения данных запроса

Segments определяет область применения схемы:

  • Segments.BODY
  • Segments.QUERY
  • Segments.PARAMS
  • Segments.HEADERS

Пример комбинированной валидации:

celebrate({
  [Segments.PARAMS]: Joi.object({
    id: Joi.string().uuid().required()
  }),
  [Segments.QUERY]: Joi.object({
    verbose: Joi.boolean()
  })
})

Разделение позволяет локализовать ошибки и упрощает поддержку маршрутов.


Структура схем Joi в Celebrate

Схемы основаны на композиции валидаторов:

Строковые правила

Joi.string().trim().min(5).max(50)

Числовые ограничения

Joi.number().integer().positive()

Сложные объекты

Joi.object({
  title: Joi.string().required(),
  content: Joi.string().required(),
  tags: Joi.array().items(Joi.string())
})

Вложенные структуры

Joi.object({
  user: Joi.object({
    id: Joi.number().required(),
    profile: Joi.object({
      nickname: Joi.string()
    })
  })
})

Обработка ошибок валидации

Celebrate генерирует ошибки при несоответствии схемам. Для централизованной обработки используется middleware обработки ошибок.

const { errors } = require('celebrate');

app.use(errors());

Формат ошибки содержит:

  • источник (body, query, params)
  • описание несоответствия
  • путь до поля
  • тип ошибки

Пример структуры ответа:

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Validation failed",
  "validation": {
    "body": {
      "source": "body",
      "keys": ["email"],
      "message": "\"email\" must be a valid email"
    }
  }
}

Кастомизация сообщений ошибок

Joi поддерживает переопределение сообщений:

Joi.string().email().messages({
  'string.email': 'Некорректный формат email'
})

Celebrate сохраняет эти сообщения в итоговом ответе, что позволяет унифицировать пользовательский интерфейс ошибок.


Валидация заголовков и безопасные API

Валидация HTTP-заголовков используется для защиты API от некорректных или вредоносных запросов:

celebrate({
  [Segments.HEADERS]: Joi.object({
    authorization: Joi.string().required()
  }).unknown()
})

Метод .unknown() необходим для разрешения стандартных заголовков, не указанных в схеме.


Интеграция с маршрутной архитектурой

В крупных приложениях Celebrate применяется на уровне маршрутизаторов:

const router = require('express').Router();

router.get(
  '/posts/:id',
  celebrate({
    [Segments.PARAMS]: Joi.object({
      id: Joi.number().required()
    })
  }),
  handler
);

Такой подход позволяет локализовать правила валидации рядом с маршрутами.


Повторное использование схем

Для масштабируемости схемы выносятся в отдельные модули:

const userSchema = Joi.object({
  username: Joi.string().required(),
  password: Joi.string().min(8)
});

module.exports = { userSchema };

Использование:

celebrate({
  [Segments.BODY]: userSchema
})

Композиция схем

Схемы можно объединять:

const baseSchema = Joi.object({
  id: Joi.number().required()
});

const extendedSchema = baseSchema.keys({
  name: Joi.string().required()
});

Такой подход снижает дублирование и упрощает сопровождение.


Асинхронная природа валидации

Celebrate выполняет проверку синхронно в контексте middleware цепочки Express. Это обеспечивает предсказуемую остановку запроса при первой ошибке валидации без обращения к бизнес-логике.


Поведение при множественных ошибках

По умолчанию Joi может собирать несколько ошибок или останавливаться на первой. Поведение регулируется опцией:

Joi.object().options({ abortEarly: false })

При отключении раннего прерывания формируется полный список нарушений схемы.


Безопасность и защита API

Использование Celebrate снижает риск:

  • SQL-инъекций через неконтролируемые поля
  • некорректных типов данных в бизнес-логике
  • переполнения структуры запроса
  • неожиданных null/undefined значений

Валидация на уровне middleware выполняет роль первого фильтра входных данных.


Паттерны применения в сложных системах

Глобальная схема ошибок

app.use(errors());
app.use((err, req, res, next) => {
  res.status(500).json({ message: 'Internal error' });
});

Разделение публичного и внутреннего API

  • публичные маршруты: строгая валидация всех параметров
  • внутренние сервисы: упрощённые схемы или частичная валидация

Ограничения подхода

Celebrate не решает задачи:

  • бизнес-валидации (например, уникальность email в базе)
  • авторизации и контроля доступа
  • преобразования сложных доменных моделей

Он ограничивается структурной проверкой входных данных.


Расширение функциональности через кастомные валидаторы

Joi позволяет определять пользовательские правила:

Joi.string().custom((value, helpers) => {
  if (!value.startsWith('APP_')) {
    return helpers.error('string.prefix');
  }
  return value;
})

Это расширяет возможности схем без изменения архитектуры middleware.