1. Как работает middleware в Express.js
Middleware (по-русски часто называют "промежуточное ПО", "промежуточный обработчик" или просто "мидлварь") — это функция, которая стоит между запросом пользователя и вашим конечным обработчиком маршрута. Она может что-то сделать с запросом, изменить его, добавить данные, проверить права, записать лог, обработать ошибку — и только потом передать управление дальше.
В Express.js middleware — это обычная функция, которая принимает три (а иногда четыре) аргумента:
function myMiddleware(req, res, next) {
// что-то делаем с req или res
next(); // передаём управление дальше
}
- req — объект запроса (Request).
- res — объект ответа (Response).
- next — специальная функция, которую нужно вызвать, чтобы передать управление следующему middleware или обработчику маршрута.
Если next() не вызвать — цепочка прервётся, и пользователь будет вечно ждать ответа (или получит таймаут). Это частая ошибка новичков.
Минимальный пример
const express = require('express');
const app = express();
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next(); // обязательно!
}
app.use(logger);
app.get('/', (req, res) => {
res.send('Hello, Middleware!');
});
Здесь каждый запрос будет проходить через logger, который просто пишет в консоль метод и URL.
2. Порядок выполнения middleware: цепочка обработки
Порядок важен!
Express строит цепочку middleware в том порядке, в каком вы их объявили в коде.
- Запрос поступает в приложение.
- Первый middleware (app.use(...) или маршрутный) получает управление.
- Если он вызывает next(), управление передаётся следующему middleware.
- Так продолжается до тех пор, пока не будет отправлен ответ (res.send, res.json и т.д.) или не закончится цепочка.
Если какой-то middleware не вызовет next() и не отправит ответ — всё зависнет. Это как если бы гардеробщик в ресторане не отдал вам номерок и не пустил дальше.
Визуальная схема
[Запрос] → [Middleware 1] → [Middleware 2] → [Маршрут/обработчик] → [Ответ]
Пример с несколькими middleware
app.use((req, res, next) => {
console.log('Первый мидлварь');
next();
});
app.use((req, res, next) => {
console.log('Второй мидлварь');
next();
});
app.get('/', (req, res) => {
res.send('Финальный ответ');
});
В консоли:
Первый мидлварь
Второй мидлварь
3. Типы middleware в Express
В Express.js есть несколько основных видов middleware:
- Глобальные (application-level) — работают для всех маршрутов:
app.use(middlewareFunction) - Маршрутные (router-level) — работают только для определённых маршрутов:
app.use('/admin', adminMiddleware) - Встроенные (built-in) — например, express.json(), express.static()
- Сторонние (third-party) — например, morgan, cors, helmet
- Обработчики ошибок — специальные middleware с 4 аргументами (err, req, res, next)
Пример глобального и маршрутного middleware
// Глобальный — для всех маршрутов
app.use((req, res, next) => {
console.log('Глобальный мидлварь');
next();
});
// Только для /admin
app.use('/admin', (req, res, next) => {
console.log('Мидлварь только для /admin');
next();
});
app.get('/', (req, res) => res.send('Главная'));
app.get('/admin', (req, res) => res.send('Админка'));
4. Практические примеры: для чего нужен middleware
Логирование всех запросов
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
next();
});
Проверка авторизации
function checkAuth(req, res, next) {
if (req.query.token === 'secret') {
next();
} else {
res.status(401).send('Не авторизован');
}
}
app.use('/private', checkAuth);
app.get('/private/data', (req, res) => {
res.send('Секретные данные!');
});
Добавление данных в объект запроса
app.use((req, res, next) => {
req.startTime = Date.now();
next();
});
app.get('/', (req, res) => {
const duration = Date.now() - req.startTime;
res.send(`Время обработки: ${duration} мс`);
});
Использование стороннего middleware
const morgan = require('morgan');
app.use(morgan('dev'));
5. Как Express выбирает, какой middleware вызвать
Express идёт по цепочке middleware сверху вниз.
Если вы используете app.use(middleware), он будет вызван для любого запроса.
Если вы используете app.use('/path', middleware), он будет вызван только для маршрутов, начинающихся с /path.
Важно:
- Если путь совпал, middleware вызовется, и после next() цепочка продолжается.
- Если путь не совпал — middleware пропускается.
- Если вы отправили ответ (res.send и т.д.) — цепочка прерывается, остальные middleware не вызываются.
6. Реализация цепочки middleware в мини-приложении
Давайте доработаем наше учебное приложение — TODO-лист на Express (или любой другой пример из прошлых лекций).
Добавим логирование, проверку авторизации и измерение времени запроса через middleware.
const express = require('express');
const app = express();
// 1. Логирование
app.use((req, res, next) => {
console.log(`[${new Date().toLocaleTimeString()}] ${req.method} ${req.url}`);
next();
});
// 2. Авторизация для приватных маршрутов
app.use('/api/private', (req, res, next) => {
if (req.query.token === '12345') {
next();
} else {
res.status(401).json({ error: 'Unauthorized' });
}
});
// 3. Засекаем время обработки
app.use((req, res, next) => {
req.requestTime = Date.now();
// Хитрость: слушаем событие "finish" на res, чтобы узнать, когда ответ отправлен
res.on('finish', () => {
const duration = Date.now() - req.requestTime;
console.log(`Время обработки: ${duration} мс`);
});
next();
});
// Обычный маршрут
app.get('/api/todos', (req, res) => {
res.json([
{ id: 1, text: 'Выучить middleware', done: false },
{ id: 2, text: 'Порадоваться успехам', done: false },
]);
});
// Приватный маршрут
app.get('/api/private/secret', (req, res) => {
res.json({ secret: '42' });
});
app.listen(3000, () => console.log('Сервер запущен на http://localhost:3000'));
Как это работает?
- Все запросы логируются.
- Для маршрутов, начинающихся с /api/private, проверяется токен.
- Для всех маршрутов измеряется время обработки.
7. Типичные ошибки при работе с middleware
Ошибка №1: забыли вызвать next()
Ваш middleware сделал свою работу, но не вызвал next() и не отправил ответ. В результате браузер будет вечно ждать ответа, а вы — искать баг.
Ошибка №2: отправили ответ, но вызвали next()
Если вы уже отправили ответ (res.send, res.json), не нужно вызывать next(), иначе следующий middleware может попытаться отправить ещё один ответ — Express ругнётся ошибкой "Can't set headers after they are sent".
Ошибка №3: неправильный порядок
Если вы подключили middleware после маршрутов, он не будет работать для уже обработанных запросов. Всегда объявляйте глобальные middleware до маршрутов!
Ошибка №4: забыли обработать ошибки
Обычные middleware не ловят ошибки, выброшенные внутри себя. Для этого нужен специальный error-handling middleware (о нём — в отдельной лекции).
Ошибка №5: не учитываете путь
Если вы хотите, чтобы middleware работал только для определённых маршрутов, не забудьте про второй аргумент в app.use('/path', middleware).
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ