Что такое middleware
Middleware — функция, которая видит запрос до обработчика и решает: обработать, изменить или передать дальше через next().
«Запрос в Express проходит конвейер: каждая станция-middleware что-то делает и зовёт следующую.»
Middleware — вторая по важности идея Express после маршрутов. Это функция, которая встраивается в поток обработки запроса между приёмом и ответом. Логирование, разбор тела, проверка авторизации, обработка ошибок — всё это middleware. Понять их — значит понять, как Express устроен на самом деле.
Анатомия middleware
Middleware — это функция трёх аргументов: (req, res, next). Она может что-то сделать с запросом или ответом, а затем либо отправить ответ, либо вызвать next(), чтобы передать управление дальше:
// простейший логгер
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next(); // передаём управление следующему
}
app.use(logger); // применить ко всем запросам
app.get('/', (req, res) => {
res.send('главная');
});Ключевой момент: если middleware не вызовет next() и не отправит ответ, запрос зависнет навсегда. next() — это эстафетная палочка.
Конвейер запроса
Каждый запрос проходит цепочку middleware по порядку, пока кто-то не отправит ответ:
запрос
|
v
[ logger ] --next()--> [ express.json ] --next()--> [ auth ]
|
next() v
[ обработчик маршрута ]
|
v
ОТВЕТСобираем конвейер в браузере
Реализуем цепочку middleware как массив функций. Каждая вызывает next, чтобы передать управление дальше — ровно как в Express:
function runPipeline(req, middlewares) {
let i = 0;
function next() {
const mw = middlewares[i++];
if (mw) mw(req, next);
}
next();
}
const logger = (req, next) => { console.log('LOG', req.url); next(); };
const addUser = (req, next) => { req.user = 'Аня'; next(); };
const handler = (req, next) => { console.log('Привет,', req.user); };
runPipeline({ url: '/home' }, [logger, addUser, handler]);
// LOG /home
// Привет, АняОбрати внимание: addUser дописал свойство в req, и обработчик его увидел. Так middleware обогащают запрос данными (например, авторизованным пользователем).
Как работает под капотом
Express хранит все middleware и маршруты в одном стеке слоёв. На каждый запрос он создаёт функцию next, которая берёт следующий слой из стека и вызывает его. Когда слой вызывает next(), цикл продолжается; когда отправляет ответ — цикл естественно завершается, потому что next больше никто не дёргает. Именно поэтому порядок подключения middleware критичен.
Частые ошибки
- Забыть next(). Запрос повиснет: ни ответа, ни передачи дальше.
- Вызвать next() после ответа. Если ты уже отправил
res.send(), лишнийnext()может привести к ошибке "headers already sent". - Неверный порядок. Middleware, который должен видеть тело, обязан стоять после
express.json().
Best practices
- Делай middleware маленькими и с одной задачей: лог, парсинг, проверка.
- Подключай глобальные middleware через
app.use()в начале файла. - Всегда либо вызывай
next(), либо отправляй ответ — но не оба сразу.
Итоги
Middleware — функция (req, res, next), звено конвейера обработки запроса. Она может изменить запрос, отправить ответ или передать эстафету через next(). Дальше научимся писать собственные middleware и привязывать их не только глобально, но и к конкретным маршрутам.
req и res живут весь запрос
Объекты req и res создаются один раз на запрос и проходят через всю цепочку middleware. Это делает их идеальным местом для передачи данных между звеньями: middleware аутентификации кладёт в req.user распознанного пользователя, и любой следующий обработчик его видит. Важно не злоупотреблять: дописывай в req только то, что действительно относится к запросу, и избегай конфликтов имён с тем, что уже кладёт Express. Хорошее правило — складывать своё в один вложенный объект, например req.context = {}, чтобы не затирать стандартные свойства и видеть, что именно добавило приложение.