Express.jsMiddleTechnical
Как реализовать rate limiting в Express?
Rate limiting в Express реализуется через express-rate-limit (в памяти) или с Redis-хранилищем через rate-limit-redis. Ограничивает число запросов по IP или пользователю за заданный промежуток времени.
Зачем нужен rate limiting
Rate limiting защищает от brute-force атак на логин, DoS-атак, скрейпинга и злоупотребления API. Без него один клиент может исчерпать ресурсы сервера или перебрать пароли за секунды.
Установка
npm install express-rate-limit
# Для распределённого окружения (несколько инстансов)
npm install rate-limit-redis ioredis
Базовый rate limiter
const express = require('express');
const rateLimit = require('express-rate-limit');
const app = express();
// Общий лимит для всего API
const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 минут
max: 100, // 100 запросов на IP
standardHeaders: 'draft-7', // RateLimit-Policy, RateLimit заголовки
legacyHeaders: false, // отключаем X-RateLimit-*
message: {
error: 'Too many requests, please try again later.',
retryAfter: 'See Retry-After header',
},
// Пропускаем доверенные IP (мониторинг, health-check)
skip: (req) => req.ip === '127.0.0.1',
});
app.use('/api/', globalLimiter);
app.listen(3000);
Строгий лимит для аутентификации
const authLimiter = rateLimit({
windowMs: 10 * 60 * 1000, // 10 минут
max: 10, // только 10 попыток
skipSuccessfulRequests: true, // не считаем успешные логины
keyGenerator: (req) => {
// Ограничиваем по IP + email вместе
const email = req.body?.email?.toLowerCase() || '';
return `${req.ip}:${email}`;
},
handler: (req, res, next, options) => {
console.warn(`Rate limit hit: ${req.ip} -> ${req.body?.email}`);
res.status(429).json({
error: 'Too many login attempts. Account temporarily locked.',
retryAfter: Math.ceil(options.windowMs / 1000),
});
},
});
app.post('/auth/login', authLimiter, loginHandler);
Redis-хранилище для нескольких инстансов
const { RedisStore } = require('rate-limit-redis');
const Redis = require('ioredis');
const redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: 6379,
});
const distributedLimiter = rateLimit({
windowMs: 60 * 1000,
max: 60,
standardHeaders: 'draft-7',
legacyHeaders: false,
store: new RedisStore({
sendCommand: (...args) => redis.call(...args),
prefix: 'rl:api:',
}),
});
app.use('/api/', distributedLimiter);
Разные лимиты по ролям пользователей
function dynamicLimit(req) {
if (req.user?.tier === 'premium') return 1000;
if (req.user?.tier === 'basic') return 200;
return 60; // анонимный
}
const tieredLimiter = rateLimit({
windowMs: 60 * 1000,
max: dynamicLimit,
keyGenerator: (req) => req.user?.id || req.ip,
standardHeaders: 'draft-7',
legacyHeaders: false,
});
// Применяем после middleware аутентификации
app.use('/api/', authenticate, tieredLimiter);
Заголовки в ответе
RateLimit-Policy: 100;w=900
RateLimit: limit=100, remaining=87, reset=1716201600
Retry-After: 300
Подводные камни
- Хранилище в памяти при горизонтальном масштабировании — каждый процесс считает свои лимиты независимо; при 4 инстансах реальный лимит в 4 раза выше. Всегда используйте Redis в production.
- Доверие заголовку X-Forwarded-For без проверки — если за nginx/балансировщиком, нужно настроить
app.set('trust proxy', 1), иначе все запросы придут с IP прокси. - Слишком мягкий лимит на /auth/login — 100 попыток за 15 минут позволяют перебирать распространённые пароли. Для логина ставьте 5–10 попыток.
- Блокировка легитимных пользователей за NAT — офисы с общим IP могут получить бан. Добавляйте возможность лимитировать по user ID после аутентификации.
- Отсутствие заголовков Retry-After — без них клиент не знает, когда повторить запрос, и делает retry-storm, усиливая нагрузку.
- Нет мониторинга срабатываний — rate limit без алертов не даёт видимости атак. Логируйте каждое срабатывание с IP и эндпоинтом.
- windowMs слишком большой — окно в 1 час при лимите 1000 означает, что burst из 1000 запросов за секунду легален. Используйте sliding window или token bucket для сглаживания.
Common mistakes
- Дает общий ответ про Node.js и не называет конкретные API Express.js.
- Не объясняет, где в lifecycle находится rate limiting в Express.
- Не разделяет validation, authorization, business logic и persistence.
- Игнорирует ошибки, лимиты входных данных, observability и тестирование.
What the interviewer is testing
- Может объяснить rate limiting в Express на примере кода.
- Называет ключевые API: express-rate-limit.
- Использует точные API Express.js, а не вымышленные hooks/decorators/methods.
- Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.