Middleware в Express: сердце фреймворка

Содержание

О проверке кода. Примеры не запускались в этой сессии (по договорённости — фокус на контенте, а не прогонах). Говорим прямо, а не ставим бейдж «проверено».

Middleware — центральная концепция Express. Понять её — значит понять, как фреймворк вообще работает: это не деталь, а весь его механизм. Разберём без тумана.

Что это: функция в конвейере

Middleware — функция, которая выполняется между получением запроса и отправкой ответа, имея доступ к трём вещам: req, res и next.

function logger(req, res, next) {
  console.log(`${req.method} ${req.url}`);   // делаем что-то с запросом
  next();                                    // передаём управление дальше
}

app.use(logger);   // подключить глобально — сработает на каждый запрос

Запрос проходит через цепочку middleware, как по конвейеру: каждое звено может проверить запрос, что-то в него добавить, залогировать, отклонить — и передать дальше через next(). Обработчик маршрута — по сути последнее middleware в цепочке.

Запрос идёт через цепочку middleware
  1. 1 Запрос входитGET /api/users
  2. 2 Логирование → next()Записали, передали дальше
  3. 3 Проверка авторизации → next()Токен ок — пропускаем; нет — отклоняем
  4. 4 Обработчик маршрута → res.jsonФормирует ответ, конец цепочки

next: без него запрос зависнет

// ❌ забыли next — запрос повиснет навсегда
app.use((req, res, next) => {
  console.log('лог');
  // next() не вызван → следующее middleware и маршрут НЕ выполнятся
});

// ✅ вызвали next — цепочка продолжается
app.use((req, res, next) => {
  console.log('лог');
  next();
});

next() передаёт управление следующему звену. Не вызвали — цепочка останавливается, запрос зависает, клиент ждёт ответа вечно. Это ошибка №1 при работе с middleware. Единственное исключение: если middleware само отправляет ответ (res.json, res.send), next не нужен — цепочка на этом завершена намеренно:

app.use((req, res, next) => {
  if (!req.headers.authorization) {
    return res.status(401).json({ error: 'Нужна авторизация' });  // ответ — конец
  }
  next();   // авторизован — пропускаем дальше
});

Порядок = порядок подключения

app.use(express.json());     // 1. сначала разобрать тело — до маршрутов
app.use(logger);             // 2. залогировать
app.use('/api', apiRouter);  // 3. маршруты
app.use(errorHandler);       // 4. обработка ошибок — В КОНЦЕ

Middleware выполняются строго в порядке подключения, сверху вниз. Отсюда правила:

  • разбор тела (express.json) и логирование — раньше маршрутов, иначе в обработчике req.body ещё не готов;
  • обработчик ошибок — в самом конце, чтобы ловить сбои всех предыдущих;
  • проверка авторизации — до защищённых маршрутов.

Перепутать порядок — получить req.body === undefined или неработающую авторизацию. Порядок здесь — не стиль, а логика.

Глобальные и маршрутные middleware

// глобальное — на ВСЕ запросы
app.use(logger);

// маршрутное — только на этот маршрут
app.get('/admin', checkAdmin, (req, res) => {   // checkAdmin выполнится перед обработчиком
  res.json({ secret: 'данные' });
});

// на группу маршрутов через префикс
app.use('/api', authenticate);   // авторизация для всего /api
  • глобальное (app.use(fn)) срабатывает на каждый запрос — логирование, разбор тела, CORS;
  • маршрутное (передано в конкретный маршрут) — только для него: проверка прав на /admin;
  • на группуapp.use('/api', fn) применяет middleware ко всем маршрутам с префиксом.

Так авторизацию вешают либо глобально, либо только на защищённые маршруты — по потребности.

Встроенные и сторонние middleware

Многое не нужно писать — оно готово:

app.use(express.json());              // встроенное: разбор JSON-тела
app.use(express.urlencoded());        // разбор данных форм
app.use(express.static('public'));    // отдача статики

const cors = require('cors');
app.use(cors());                      // стороннее: заголовки CORS
const helmet = require('helmet');
app.use(helmet());                    // защитные заголовки безопасности

Экосистема Express — это во многом библиотека middleware. Разбор тела, CORS, защитные заголовки (helmet), сжатие, сессии, загрузка файлов — всё это готовые middleware, которые подключаются одной строкой. В этом сила Express: нужную возможность обычно уже кто-то оформил как middleware.

Своё middleware

// замер времени обработки запроса
function timing(req, res, next) {
  const start = Date.now();
  res.on('finish', () => {              // когда ответ отправлен
    console.log(`${req.method} ${req.url}${Date.now() - start}ms`);
  });
  next();
}

// добавить данные в запрос для следующих middleware
function attachUser(req, res, next) {
  req.user = decodeToken(req.headers.authorization);   // положили в req
  next();   // обработчик дальше прочитает req.user
}

app.use(timing);
app.use(attachUser);

Своё middleware — обычная функция (req, res, next). Частый приём — дополнить req данными (req.user) для следующих звеньев: одно middleware декодирует токен и кладёт пользователя в req, а обработчики маршрутов его читают. Так общая логика (авторизация, замер, контекст) выносится из каждого маршрута в одно переиспользуемое место.


Смежные темы: Express с нуля — основы; маршрутизация; обработка ошибок — особое middleware; JWT-авторизация — middleware проверки. Полный список — в уроках Node.js.

Частые вопросы

Что такое middleware в Express?
Функция, которая выполняется между получением запроса и отправкой ответа, имея доступ к req, res и функции next. Middleware проверяет, изменяет или дополняет запрос, а затем через next передаёт управление дальше. На них построен весь Express.
Что делает функция next?
Передаёт управление следующему middleware или обработчику в цепочке. Без вызова next запрос зависнет, потому что цепочка остановится. Вызов next с аргументом-ошибкой перескакивает сразу к обработчику ошибок.
Важен ли порядок middleware?
Да, middleware выполняются в порядке подключения сверху вниз. Поэтому разбор тела запроса и логирование ставят раньше маршрутов, а обработчик ошибок — в самом конце. Неверный порядок ломает логику.
Чем глобальный middleware отличается от маршрутного?
Глобальный подключают через app.use, и он срабатывает на все запросы. Маршрутный указывают у конкретного маршрута, и он работает только для него. Например, проверка авторизации может быть глобальной или только на защищённых маршрутах.
Как написать своё middleware?
Создать функцию с параметрами req, res, next, выполнить нужную логику и вызвать next для продолжения. Например, залогировать запрос или проверить токен. Подключают её через app.use или передают в маршрут.