1. Что такое пользовательский middleware?
Middleware в Express — это, по сути, функции, которые получают на вход три (или иногда четыре) аргумента: req, res и next. Они могут:
- изменять объекты запроса или ответа;
- выполнять дополнительную логику (например, логирование, проверку авторизации, обработку ошибок);
- завершать обработку запроса (отправлять ответ);
- передавать управление следующему middleware с помощью вызова next().
Пользовательский middleware — это просто middleware, написанный вами, а не встроенный в Express.
Если представить обработку запроса как эстафету, то каждый middleware — это бегун, который либо добегает до финиша (отправляет ответ), либо передаёт эстафетную палочку дальше (next()).
Синтаксис пользовательского middleware
Давайте посмотрим на базовый шаблон пользовательского middleware:
function myMiddleware(req, res, next) {
// Ваша логика здесь
next(); // Передаём управление дальше по цепочке
}
- req — объект запроса.
- res — объект ответа.
- next — функция, которую нужно вызвать, чтобы передать управление следующему middleware или роуту.
Если middleware не вызывает next() и не отправляет ответ, то запрос "зависнет" (Express будет ждать, когда обработка закончится).
2. Первый пример: простое логирование
Начнём с самого частого: логирование запросов.
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next(); // Не забываем передать управление дальше!
}
Чтобы middleware начал работать, его нужно "подключить" к приложению с помощью app.use():
const express = require('express');
const app = express();
app.use(logger);
app.get('/', (req, res) => {
res.send('Главная страница');
});
Теперь при каждом запросе в консоли будет появляться строка вида GET / или POST /login.
3. Middleware для ограничения доступа (авторизация)
Рассмотрим пример middleware, который проверяет наличие авторизационного токена.
function authMiddleware(req, res, next) {
const token = req.headers['authorization'];
if (!token) {
res.status(401).send('Нет доступа!');
// next() не вызываем — обработка запроса на этом заканчивается
return;
}
// Можно сохранить данные о пользователе в req для дальнейших middleware/роутов
req.user = { name: 'TestUser' }; // Примерно, обычно тут будет декодирование токена
next();
}
Подключаем только к защищённым маршрутам:
app.get('/private', authMiddleware, (req, res) => {
res.send(`Привет, ${req.user.name}! Это приватная страница.`);
});
Если запрос приходит без заголовка Authorization, пользователь получит 401 ошибку, а не попадёт в обработчик маршрута.
5. Полезные нюансы
Middleware с параметрами (фабрика middleware)
Иногда хочется, чтобы middleware был настраиваемым. Например, логгер, который пишет в файл или консоль, или авторизация с разными уровнями доступа. Для этого используют функцию-"фабрику", которая возвращает middleware.
function greetMiddleware(name) {
return function (req, res, next) {
console.log(`Привет, ${name}!`);
next();
};
}
app.use(greetMiddleware('Василий'));
Это удобно, если вам нужно несколько похожих middleware с разными параметрами.
Middleware для обработки ошибок
Особый вид middleware — обработчик ошибок. Он отличается тем, что принимает четыре аргумента: err, req, res, next.
function errorHandler(err, req, res, next) {
console.error('Произошла ошибка:', err);
res.status(500).send('Что-то пошло не так!');
}
Такие middleware подключаются в самом конце цепочки:
app.use(errorHandler);
Если где-то в цепочке middleware или в роуте вызвать next(err), Express передаст управление такому обработчику.
Порядок и область применения middleware
Важно помнить: middleware вызываются в том порядке, в котором они подключены. Если вы сначала подключили логгер, а потом авторизацию, то логгер сработает первым.
Также middleware можно подключать не для всего приложения, а только для отдельных маршрутов или групп маршрутов:
// Только для /admin
app.use('/admin', adminAuthMiddleware);
// Только для одного маршрута
app.get('/profile', profileMiddleware, (req, res) => { ... });
6. Практика: развиваем наше демо-приложение
Представим, что мы делаем мини-блог. В нашем приложении уже есть базовая структура:
const express = require('express');
const app = express();
app.use(express.json()); // встроенный middleware для JSON
const posts = [
{ id: 1, title: 'Первый пост', content: 'Hello, world!' }
];
app.get('/posts', (req, res) => {
res.json(posts);
});
Добавим пользовательский middleware для логирования и авторизации:
// Логгер
function logger(req, res, next) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
}
app.use(logger);
// Авторизация (допустим, только POST-запросы требуют авторизации)
function requireAuth(req, res, next) {
if (req.method === 'POST') {
const token = req.headers['authorization'];
if (token !== 'secret') {
res.status(403).json({ error: 'Нет доступа' });
return;
}
}
next();
}
app.use(requireAuth);
// Добавление поста
app.post('/posts', (req, res) => {
const { title, content } = req.body;
if (!title || !content) {
res.status(400).json({ error: 'Не хватает данных' });
return;
}
const newPost = { id: posts.length + 1, title, content };
posts.push(newPost);
res.status(201).json(newPost);
});
- Все запросы логируются.
- Для POST-запросов к /posts требуется заголовок Authorization: secret.
- Ошибки (например, отсутствие данных) возвращают корректные статусы.
7. Типичные ошибки при создании middleware
Ошибка №1: забыли вызвать next()
Самая частая и коварная ошибка — забыть вызвать next() (или отправить ответ). В результате запрос "зависает", браузер крутит "вечную загрузку", а вы начинаете подозревать, что сервер устал от жизни. Всегда проверяйте, что в каждом сценарии либо вызывается next(), либо отправляется ответ.
Ошибка №2: несколько раз отправили ответ
Если случайно вызвать res.send() несколько раз, Express начнёт ругаться: Cannot set headers after they are sent to the client. Убедитесь, что после отправки ответа выполнение функции завершается (return), чтобы не "провалиться" дальше по цепочке.
Ошибка №3: неправильный порядок подключения
Если вы подключили middleware после маршрутов, он не сработает для этих маршрутов. Всегда размещайте глобальные middleware до объявления маршрутов, если хотите, чтобы они применялись ко всем запросам.
Ошибка №4: забыли про асинхронность
Если ваш middleware делает что-то асинхронное (например, обращается к базе данных), не забудьте обработать промисы или ошибки. В Express 4.x обычные функции middleware не "понимают" промисы — используйте async-функции и ловите ошибки вручную или передавайте их в next(err).
app.use(async (req, res, next) => {
try {
await чтоТоАсинхронное();
next();
} catch (err) {
next(err); // Передаём ошибку в error handler
}
});
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ