11. Middleware

Middleware — это функции, которые выполняются между получением запроса и отправкой ответа. Основа архитектуры Express.
Как работает Middleware
Заголовок раздела «Как работает Middleware»Запрос → MW1 → MW2 → MW3 → Роут → Ответ ↓ ↓ ↓ ↓ next next next res.json()// Подпись middleware функцииfunction myMiddleware(req, res, next) { // Что-то делаем с запросом/ответом console.log(`${req.method} ${req.url}`);
// Передаём управление следующему middleware next();
// Или прерываем цепочку отправив ответ // res.status(401).json({ error: 'Unauthorized' });
// Или передаём ошибку // next(new Error('Something went wrong'));}Типы Middleware
Заголовок раздела «Типы Middleware»// 1. Глобальный — применяется ко всем роутамapp.use(myMiddleware);
// 2. Для конкретного путиapp.use('/api', apiMiddleware);app.use('/admin', adminMiddleware);
// 3. Для конкретного роутаapp.get('/profile', authMiddleware, profileHandler);
// 4. Несколько MW для роутаapp.post('/upload', authMiddleware, rateLimitMiddleware, fileValidationMiddleware, uploadHandler);
// 5. Error middleware — 4 параметра!app.use((err, req, res, next) => { res.status(500).json({ error: err.message });});Популярные Middleware пакеты
Заголовок раздела «Популярные Middleware пакеты»const express = require('express');const cors = require('cors');const helmet = require('helmet');const morgan = require('morgan');const compression = require('compression');const rateLimit = require('express-rate-limit');
const app = express();
// Безопасность HTTP заголовковapp.use(helmet());
// CORS — разрешаем запросы с других доменовapp.use(cors({ origin: ['https://mysite.com', 'http://localhost:3000'], credentials: true,}));
// Логирование запросовapp.use(morgan('combined')); // подробный форматapp.use(morgan('dev')); // красивый для разработки
// Парсинг тела запросаapp.use(express.json({ limit: '10mb' }));app.use(express.urlencoded({ extended: true }));
// Сжатие ответов gzipapp.use(compression());
// Rate limiting — защита от DDoSapp.use(rateLimit({ windowMs: 15 * 60 * 1000, // 15 минут max: 100, // максимум 100 запросов message: 'Слишком много запросов, попробуй позже',}));Создание своих Middleware
Заголовок раздела «Создание своих Middleware»Логирование запросов
Заголовок раздела «Логирование запросов»function logger(req, res, next) { const start = Date.now();
// Перехватываем finish события res.on('finish', () => { const duration = Date.now() - start; const color = res.statusCode >= 400 ? '\x1b[31m' : '\x1b[32m'; console.log( `${color}${req.method}\x1b[0m ${req.originalUrl} ${res.statusCode} ${duration}ms` ); });
next();}
module.exports = logger;Проверка авторизации
Заголовок раздела «Проверка авторизации»const jwt = require('jsonwebtoken');const { JWT_SECRET } = require('../config');
function authenticate(req, res, next) { const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Токен не предоставлен' }); }
const token = authHeader.slice(7);
try { const payload = jwt.verify(token, JWT_SECRET); req.user = payload; // добавляем данные юзера в req next(); } catch (err) { if (err.name === 'TokenExpiredError') { return res.status(401).json({ error: 'Токен истёк' }); } res.status(401).json({ error: 'Неверный токен' }); }}
// Проверка ролиfunction requireRole(...roles) { return (req, res, next) => { if (!req.user) { return res.status(401).json({ error: 'Не авторизован' }); } if (!roles.includes(req.user.role)) { return res.status(403).json({ error: 'Недостаточно прав' }); } next(); };}
module.exports = { authenticate, requireRole };Валидация запроса
Заголовок раздела «Валидация запроса»const { z } = require('zod');
function validate(schema, source = 'body') { return (req, res, next) => { const result = schema.safeParse(req[source]);
if (!result.success) { return res.status(400).json({ error: 'Ошибка валидации', details: result.error.flatten().fieldErrors, }); }
req[source] = result.data; // нормализованные данные next(); };}
module.exports = { validate };// Использованиеconst { z } = require('zod');const { authenticate, requireRole } = require('./middleware/auth');const { validate } = require('./middleware/validate');
const createUserSchema = z.object({ name: z.string().min(2).max(50), email: z.string().email(), password: z.string().min(8), role: z.enum(['user', 'admin']).default('user'),});
app.post('/api/users', authenticate, requireRole('admin'), validate(createUserSchema), createUser);Обработка ошибок
Заголовок раздела «Обработка ошибок»function errorHandler(err, req, res, next) { console.error(err.stack);
// Известные ошибки с кодом if (err.status || err.statusCode) { return res.status(err.status || err.statusCode).json({ error: err.message, }); }
// Ошибки базы данных (Prisma) if (err.code === 'P2002') { return res.status(409).json({ error: 'Запись уже существует', }); }
if (err.code === 'P2025') { return res.status(404).json({ error: 'Запись не найдена', }); }
// Ошибки валидации Zod if (err.name === 'ZodError') { return res.status(400).json({ error: 'Ошибка валидации', details: err.flatten().fieldErrors, }); }
// Неизвестная ошибка — 500 const isDev = process.env.NODE_ENV === 'development'; res.status(500).json({ error: isDev ? err.message : 'Внутренняя ошибка сервера', ...(isDev && { stack: err.stack }), });}
module.exports = { errorHandler };Async middleware — важный нюанс
Заголовок раздела «Async middleware — важный нюанс»// ❌ Ошибка не перехватится в Express 4!app.get('/data', async (req, res) => { const data = await someAsyncOperation(); // если тут ошибка... res.json(data); // Express её не поймает!});
// ✅ Вариант 1: обёртка try/catchapp.get('/data', async (req, res, next) => { try { const data = await someAsyncOperation(); res.json(data); } catch (error) { next(error); // передаём в error handler }});
// ✅ Вариант 2: утилита asyncHandlerfunction asyncHandler(fn) { return (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); };}
app.get('/data', asyncHandler(async (req, res) => { const data = await someAsyncOperation(); res.json(data);}));
// ✅ Вариант 3: Express 5 (async поддержка из коробки)// npm install express@nextПрактика
Заголовок раздела «Практика»- Создай middleware для логирования с временем ответа
- Напиши middleware
authenticateчерез JWT - Реализуй middleware
validate(schema)для валидации тела запроса через Zod - Создай централизованный
errorHandlerдля всех ошибок приложения - Создай
asyncHandlerобёртку и примени её ко всем роутам