Свои и сторонние middleware

Свои middleware, привязка к маршруту, готовые пакеты вроде morgan и cors.
«Хороший middleware решает одну задачу и переиспользуется в десятке маршрутов.»

Теперь, когда механика ясна, научимся применять middleware прицельно: не только глобально, но и к конкретным маршрутам. Это позволяет, например, защитить только админские эндпоинты или логировать только платежи. Заодно познакомимся с экосистемой готовых middleware-пакетов.

Уровни применения

Middleware можно подключить тремя способами:

// 1. Глобально -- для всех запросов
app.use(logger);

// 2. Для конкретного пути и его подпутей
app.use('/admin', checkAdmin);

// 3. Только для одного маршрута (несколько обработчиков через запятую)
app.get('/secret', requireAuth, (req, res) => {
  res.send('секретные данные');
});

В третьем случае requireAuth выполнится перед основным обработчиком; если он не вызовет next() (например, ответит 401), до обработчика дело не дойдёт.

Пишем свой middleware

Полезный приём — middleware-фабрика: функция, возвращающая middleware с нужной настройкой. Так делают многие пакеты:

// фабрика: создаёт проверку конкретной роли
function requireRole(role) {
  return function (req, res, next) {
    if (req.user && req.user.role === role) {
      next();
    } else {
      res.status(403).json({ error: 'нет доступа' });
    }
  };
}

app.delete('/users/:id', requireRole('admin'), (req, res) => {
  res.status(204).end();
});

Цепочка проверок в браузере

Смоделируем привязку middleware к маршруту: массив проверок выполняется до обработчика, и любая может прервать цепочку. Это сердце авторизации:

function runChain(ctx, chain) {
  for (const fn of chain) {
    const ok = fn(ctx);
    if (!ok) {
      console.log('Прервано на проверке:', fn.name);
      return;
    }
  }
  console.log('Доступ разрешён, выполняем обработчик');
}

const isLoggedIn = ctx => ctx.user != null;
const isAdmin = ctx => ctx.user && ctx.user.role === 'admin';

runChain({ user: { role: 'user' } }, [isLoggedIn, isAdmin]);
// Прервано на проверке: isAdmin
runChain({ user: { role: 'admin' } }, [isLoggedIn, isAdmin]);
// Доступ разрешён, выполняем обработчик

Готовые пакеты

Не всё нужно писать самому. Популярные middleware из npm:

ПакетНазначение
morganлогирование запросов
corsразрешение кросс-доменных запросов
helmetзащитные HTTP-заголовки
express-rate-limitограничение частоты запросов

Как работает под капотом

Когда ты передаёшь middleware в app.use('/admin', ...), Express добавляет слой с префиксом пути. При запросе он сначала проверяет, начинается ли url с этого префикса, и только потом запускает middleware. Несколько обработчиков в одном маршруте — это просто несколько слоёв подряд с одинаковым путём; next() ведёт от одного к следующему.

Частые ошибки

  • Тяжёлая логика в глобальном middleware. Она выполнится на каждый запрос, включая статику. Привязывай дорогие проверки точечно.
  • Забыть вернуть next() в фабрике. Лёгко написать проверку и забыть про "счастливый путь" — тогда все запросы зависнут.
  • Слепо ставить пакеты. Каждый middleware — это и зависимость, и потенциальная уязвимость. Ставь только нужное.

Best practices

  • Выноси повторяющуюся логику (проверка прав, валидация) в переиспользуемые middleware.
  • Используй фабрики для параметризуемых проверок ролей и доступа.
  • Применяй дорогие middleware только к тем маршрутам, где они реально нужны.

Итоги

Middleware можно подключать глобально, по пути и к одному маршруту. Фабрики делают их настраиваемыми, а экосистема npm закрывает типовые задачи. Дальше разберём особый вид middleware — обработчик ошибок, у которого на один аргумент больше.

Порядок — это контракт

Поскольку middleware выполняются строго по порядку подключения, их последовательность фактически становится частью архитектуры. Канонический порядок таков: сначала безопасность (helmet, cors, rate-limit), затем парсеры тела, потом логирование, дальше аутентификация, затем маршруты и в самом конце — обработчик ошибок. Перепутаешь — и, например, лимитер начнёт считать запросы уже после тяжёлого парсинга, теряя смысл, или логгер не увидит распознанного пользователя. Воспринимай список app.use(...) в начале файла как описание конвейера: его читают сверху вниз, и каждая строка добавляет одну станцию обработки.

Проверьте себя
1. Что делает middleware-фабрика requireRole('admin')?
AСразу проверяет роль при объявлении
BВозвращает настроенный middleware, который проверит роль при каждом запросе
CМеняет роль пользователя
DЛогирует запрос
2. Как ограничить middleware только маршрутами, начинающимися с /admin?
Aapp.use(checkAdmin)
Bapp.use('/admin', checkAdmin)
Capp.admin(checkAdmin)
DЭто невозможно