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 в цепочке.
- 1 Запрос входитGET /api/users
- 2 Логирование → next()Записали, передали дальше
- 3 Проверка авторизации → next()Токен ок — пропускаем; нет — отклоняем
- 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.