Middleware в Node.js: как устроена цепочка обработчиков
Содержание
О проверке кода. Примеры не запускались — в отличие от материалов по Node.js с фактическим выводом. Код дан по документации Connect и Express. Говорим прямо, а не ставим бейдж «проверено».
Connect — библиотека, о которой сегодня почти не вспоминают, но её идея лежит в основе того, как в Node устроен любой веб-фреймворк. Разберём саму идею — она важнее библиотеки.
Что такое middleware
Middleware — это функция, через которую проходит запрос по пути к ответу. Их выстраивают в цепочку, и каждая делает ровно одно дело.
Представьте HTTP-запрос как посылку на конвейере. Первый пост проверяет отправителя (авторизация), второй записывает в журнал (логирование), третий распаковывает содержимое (разбор тела), и только потом посылка доходит до обработчика, который её обслужит.
// middleware — обычная функция трёх аргументов
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next(); // передаём запрос дальше по цепочке
}
Три параметра: req (запрос), res (ответ), next (передать управление следующему).
Ключевое: next() или ответ
У каждого middleware ровно два законных исхода:
function auth(req, res, next) {
if (!req.headers.authorization) {
res.writeHead(401).end('Нет доступа'); // ← завершаем цепочку
return;
}
next(); // ← или пропускаем дальше
}
Либо next() — передать дальше, либо ответ — оборвать цепочку. Третьего нет, и здесь живёт главная ошибка.
- 1 Пришёл запросОтдаётся первому middleware в цепочке
-
2
ЛогированиеЗаписали и вызвали
next()— идём дальше -
3
АвторизацияНет прав →
res.end(401), цепочка обрывается -
4
Разбор телаСобрали
req.body,next() - 5 Обработчик маршрутаФормирует и отправляет ответ
Забыли next() и не отправили ответ — запрос завис. Middleware получил управление и не отдал его никому. Клиент ждёт до таймаута, а вы ищете, почему «сервер молчит». Это причина номер один зависаний в Express-приложениях.
Порядок решает всё
Middleware выполняются строго сверху вниз, в порядке подключения. Это не деталь, а логика работы.
app.use(logger); // 1. сначала логируем всё
app.use(bodyParser); // 2. потом разбираем тело
app.use(auth); // 3. потом проверяем доступ
app.use(routes); // 4. и только теперь маршруты
Переставьте auth и routes — и защита окажется после выдачи данных, то есть бесполезной. Поставьте bodyParser после обработчика, который читает req.body, — тело будет пустым.
Практическое правило: общее — раньше, специфичное — позже. Логирование и безопасность вверху, конкретные маршруты внизу.
Обработка ошибок: четвёртый аргумент
Особый вид middleware, который различают по числу параметров:
// обычный middleware — 3 аргумента
function normal(req, res, next) { }
// обработчик ошибок — 4 аргумента, первый err
function errorHandler(err, req, res, next) {
console.error(err.stack);
res.writeHead(500).end('Что-то пошло не так');
}
Express отличает обработчик ошибок именно по четырём параметрам — это не метафора, а буквально подсчёт fn.length. Такой middleware вызывается, только когда предыдущий передал ошибку: next(err) с аргументом.
Ставят его последним, чтобы он ловил всё, что упало выше по цепочке. Это тот же принцип, что обработка error в EventEmitter: одно место, куда стекаются все сбои.
Как это выглядит в Express
Connect почти никто не подключает напрямую, но его модель вы используете каждый раз, когда пишете на Express:
import express from 'express';
const app = express();
app.use(express.json()); // разбор JSON — встроенный middleware
app.use((req, res, next) => { // свой логгер
console.log(req.method, req.url);
next();
});
app.get('/api/users', (req, res) => { // обработчик маршрута — тоже middleware
res.json([{ name: 'Аня' }]);
});
app.use((err, req, res, next) => { // обработчик ошибок — в конце
res.status(500).json({ error: err.message });
});
Всё здесь — middleware. И express.json(), и логгер, и обработчик маршрута, и ловец ошибок — звенья одной цепочки. Это и есть наследие Connect: не библиотека, а способ думать о запросе как о потоке через обработчики.
Что было на этой странице в 2016 году
Первая версия вышла 31 декабря 2016 года и учила middleware на самом Connect: const connect = require('connect'), app.use(...), цепочка обработчиков. Для конца 2016 года — точно и по делу.
Что изменилось:
- Connect как библиотека вышел из употребления. Его не берут в новые проекты — как Sails.js и Memcached, он не сломался, просто его роль занял Express. Показательно, что Express изначально и был построен поверх Connect.
- Модель middleware, наоборот, стала фундаментом. То, что в 2016-м было особенностью конкретной библиотеки, сегодня — общий язык всего Node-веба: Express, Koa, Fastify — все про цепочки обработчиков.
Поэтому мы переписали урок не про Connect, а про саму идею middleware. Библиотека устарела — концепция стала стандартом, и именно её нужно понимать сегодня, на каком бы фреймворке вы ни писали.
Смежные темы: HTTP-сервер без фреймворков — что middleware надстраивает; Sails.js — та же история про смену выбора; события. Полный список — в справочнике по Node.js.