Middleware: устройство и порядок вызова
Самый частый вопрос по Express: «Что такое middleware и в каком порядке они выполняются?» Ответ проверяется тут же задачкой на порядок вывода.
Middleware — функция с сигнатурой
(req, res, next), которая получает управление до обработчика маршрута. Она может изменитьreqиres, завершить запрос ответом или передать управление дальше вызовомnext().
Что проверяет интервьюер
Понимаете ли вы, что Express — это, по сути, массив функций и указатель на текущую. Отсюда следуют все практические вопросы: почему запрос «повис», почему req.body оказался undefined, почему обработчик ошибок не срабатывает и откуда берётся «Cannot set headers after they are sent».
Модель: стек и next()
Express хранит middleware в списке в порядке регистрации. Вызов next() сдвигает указатель на следующий элемент. Если next() не вызвать и не отправить ответ, запрос будет висеть до таймаута клиента. Механика ровно такая, и её легко воспроизвести на чистом синхронном JavaScript — этот пример можно запустить:
const stack = [];
const use = (fn) => stack.push(fn);
function handle(req, res) {
let i = 0;
function next() {
const fn = stack[i++];
if (!fn) return;
fn(req, res, next);
}
next();
}
use((req, res, next) => {
console.log('1 logger: пришёл запрос', req.url);
next();
console.log('1 logger: ответ сформирован, статус', res.status);
});
use((req, res, next) => {
if (!req.user) {
res.status = 401;
console.log('2 auth: пользователь не авторизован, дальше не пускаем');
return; // next() не вызываем — цепочка обрывается
}
next();
});
use((req, res, next) => {
console.log('3 handler: сюда управление не дойдёт');
});
handle({ url: '/orders', user: null }, { status: 200 });
Вывод:
1 logger: пришёл запрос /orders
2 auth: пользователь не авторизован, дальше не пускаем
1 logger: ответ сформирован, статус 401
Обратите внимание на порядок: логгер получил управление первым и снова получил его последним — после того как next() вернулся. Эту схему называют «луковицей»: код до next() выполняется на пути внутрь, код после — на пути наружу. В Express это работает для синхронного кода; для замера времени ответа обычно вешают обработчик на событие res.on('finish'), потому что реальная отправка ответа асинхронна.
Порядок регистрации решает всё
const express = require('express');
const app = express();
app.use(express.json()); // 1. разбор JSON — до маршрутов
app.use(requestId); // 2. свои сквозные middleware
app.use('/api/admin', requireAdmin); // 3. middleware, ограниченный путём
app.get('/api/orders', listOrders); // 4. маршруты
app.post('/api/orders', createOrders);
app.use((req, res) => res.status(404).json({ error: 'not found' })); // 5. 404
app.use(errorHandler); // 6. обработчик ошибок — всегда последним
Если express.json() подключить после маршрутов, req.body в них будет undefined — классический баг новичка. Если обработчик ошибок зарегистрировать раньше маршрутов, он никогда не сработает: ошибки идут по стеку вперёд, а не назад.
Виды middleware
| Вид | Пример |
| Глобальное | app.use(cors()) — на все запросы |
| Привязанное к пути | app.use('/api', apiRouter) |
| Маршрутное | app.get('/orders', auth, listOrders) — только для маршрута |
| Обработчик ошибок | (err, req, res, next) — ровно четыре аргумента |
| Встроенное / стороннее | express.static, helmet, morgan |
Отдельно подчеркните на интервью: обработчик ошибок Express опознаёт по количеству аргументов. Уберёте неиспользуемый next из сигнатуры — функция станет обычным middleware и перестанет ловить ошибки.
Две классические ошибки
// 1. вызвали next() и продолжили отвечать
app.use((req, res, next) => {
next();
res.send('ой'); // маршрут уже отправил ответ -> ERR_HTTP_HEADERS_SENT
});
// 2. забыли и next(), и ответ — запрос висит до таймаута клиента
app.use((req, res, next) => {
logger.info(req.url); // тишина: управление никуда не передано
});
Ошибка ERR_HTTP_HEADERS_SENT означает ровно одно: на один запрос попытались ответить дважды. Обычно причина в забытом return перед res.json(...) или в вызове next() после отправки ответа.
Типичные ошибки кандидатов
- Не могут объяснить, что делает
next(), и говорят «переходит к следующему маршруту». - Не знают, что порядок регистрации определяет порядок выполнения.
- Забывают про четырёхаргументную сигнатуру обработчика ошибок.
- Не видят разницы между
next()иnext(err): второй перепрыгивает сразу к обработчику ошибок.
Как ответить кратко
Middleware — функция
(req, res, next)в цепочке обработки запроса. Express хранит их в списке и выполняет строго в порядке регистрации:next()передаёт управление следующей,next(err)— сразу обработчику ошибок, а если не сделать ни того ни другого и не ответить, запрос повиснет. Парсеры вродеexpress.json()ставят до маршрутов, обработчик ошибок с четырьмя аргументами — последним. Двойной ответ на один запрос даётERR_HTTP_HEADERS_SENT.