Back

Как работает Middleware в Node.js

Как работает Middleware в Node.js

Вы видели app.use() по всему коду Express. Вы знаете, что middleware находится между запросами и ответами. Но когда что-то ломается в этой цепочке — запрос зависает, ошибка исчезает или обработчики срабатывают в неправильном порядке — ментальная модель разваливается.

Эта статья объясняет, как на самом деле работает middleware в Node.js: что это такое, как управление проходит через цепочку и что изменилось в Express 5.

Ключевые выводы

  • Middleware — это паттерн фреймворка, а не функция Node.js — Express реализует его поверх базового модуля http
  • Порядок выполнения имеет значение: middleware выполняется в порядке регистрации, а обработчики ошибок должны идти последними
  • Функция next() управляет потоком; пропуск её без отправки ответа приводит к зависанию запросов
  • Express 5 нативно перехватывает отклонённые промисы из асинхронных middleware и обработчиков маршрутов, устраняя необходимость в ручных обёртках

Middleware — это паттерн фреймворка, а не функция Node.js

В самом Node.js нет концепции middleware. Базовый модуль http предоставляет вам объекты request и response — не более того. Middleware — это паттерн, который фреймворки вроде Express реализуют поверх Node.js.

Middleware в Express следует определённой структуре: функции, которые получают req, res и next. Фреймворк поддерживает стек этих функций и вызывает их последовательно для каждого входящего запроса. Этот жизненный цикл запроса во фреймворках Node.js и даёт middleware его силу.

Другие фреймворки реализуют похожие паттерны по-разному. Koa использует «луковичную модель», где middleware оборачивается вокруг последующих обработчиков. Fastify использует хуки в определённых точках жизненного цикла. Концепция переносится, но семантика — нет.

Жизненный цикл запроса-ответа

Когда запрос попадает в приложение Express, он входит в конвейер:

  1. Express сопоставляет запрос с зарегистрированными middleware и маршрутами
  2. Каждая функция middleware выполняется в порядке регистрации
  3. Управление передаётся вперёд через next() или останавливается, когда отправляется ответ
  4. Middleware обработки ошибок перехватывает ошибки, переданные в next(err), а также ошибки, выброшенные или отклонённые внутри управляемых Express middleware и обработчиков маршрутов

Функция next() — это механизм, который перемещает управление по цепочке. Вызовите её, и Express запустит следующий подходящий middleware. Пропустите её без отправки ответа, и запрос зависнет на неопределённое время.

Порядок middleware имеет значение

Middleware в Express выполняется в том порядке, в котором вы его регистрируете. Это не просто деталь — это основа паттернов middleware в Node.js.

app.use(parseBody)
app.use(authenticate)
app.use(authorize)
app.get('/data', handler)

Здесь parseBody выполняется первым, делая данные запроса доступными для authenticate. Поменяйте их местами, и аутентификация сломается, потому что тело запроса ещё не было разобрано.

Этот порядок также влияет на обработку ошибок. Middleware обработки ошибок должен идти после маршрутов, которые он защищает.

Прерывание ответа

Middleware может завершить цикл запрос-ответ досрочно, отправив ответ без вызова next(). Так middleware аутентификации отклоняет неавторизованные запросы:

function requireAuth(req, res, next) {
  if (!req.user) {
    return res.status(401).json({ error: 'Unauthorized' })
  }
  next()
}

После выполнения res.send(), res.json() или подобных методов последующий middleware не будет выполняться для этого запроса. Это прерывание намеренно и полезно.

Middleware уровня приложения, роутера и маршрута

Middleware в Express подключается на разных уровнях:

Middleware уровня приложения выполняется для каждого запроса к приложению. Используйте app.use() для сквозных задач, таких как логирование или парсинг тела запроса.

Middleware уровня роутера выполняется для запросов, соответствующих определённому роутеру. Это ограничивает область действия middleware группами маршрутов без влияния на всё приложение.

Middleware уровня маршрута выполняется только для конкретных маршрутов. Передавайте функции middleware непосредственно в определения маршрутов для целевого поведения, такого как валидация.

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

Обработка ошибок и middleware в Express 5

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

Express 5 изменяет работу с асинхронными ошибками. В Express 4 отклонённые промисы и асинхронные исключения требовали ручной обработки или сторонних обёрток. Express 5 нативно перехватывает отклонённые промисы из асинхронных функций и направляет их к обработчикам ошибок автоматически.

// Express 5: обёртка не нужна
app.get('/data', async (req, res) => {
  const data = await fetchData() // отклонение запускает middleware обработки ошибок
  res.json(data)
})

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

Middleware обработки ошибок всё ещё нужно регистрировать последним, после маршрутов и другого middleware.

Заключение

Middleware — это паттерн уровня фреймворка для обработки запросов через цепочку функций. Понимание порядка выполнения, роли next() и места размещения обработчиков ошибок устраняет большинство проблем с отладкой middleware.

Нативная обработка асинхронных ошибок в Express 5 устраняет распространённую проблему, но основы остаются: регистрируйте middleware в правильном порядке, вызывайте next() или отправляйте ответ, и размещайте обработчики ошибок в конце.

Часто задаваемые вопросы

Если вы забудете вызвать next() и не отправите ответ, запрос зависнет на неопределённое время. Express ждёт либо вызова next() для продолжения цепочки, либо отправки ответа клиенту. Всегда убеждайтесь, что ваш middleware либо вызывает next() для передачи управления дальше, либо отправляет ответ для завершения цикла.

Да, но вы должны обрабатывать ошибки вручную. Express 4 не перехватывает отклонённые промисы автоматически, поэтому необработанные отклонения не запустят ваш middleware обработки ошибок. Оборачивайте асинхронный код в блоки try-catch или используйте функцию-обёртку, которая передаёт ошибки в next(). Express 5 устраняет это требование, перехватывая отклонения нативно.

Middleware обработки ошибок требует ровно четыре параметра: err, req, res и next. Если вы пропустите какой-либо параметр, Express будет рассматривать его как обычный middleware и пропустит его при обработке ошибок. Также убедитесь, что ваш обработчик ошибок зарегистрирован после всех маршрутов и другого middleware в вашем приложении.

Оба регистрируют middleware с одинаковой сигнатурой функции, но различаются по области действия. app.use() подключает middleware на уровне приложения, выполняясь для всех запросов. router.use() подключает middleware к конкретному экземпляру роутера, выполняясь только для запросов, соответствующих пути монтирования этого роутера.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay