Ограничение запросов (rate limiting) в Express

Краткое введение
Ограничение частоты запросов — это стратегия управления трафиком. Оно ограничивает число запросов от одного клиента за заданный промежуток времени. Существуют разные алгоритмы (фиксированное окно, скользящее окно, токен-ведро и др.), у каждого свои плюсы и минусы. В примерах ниже мы используем простой и распространённый подход с middleware для Express.
Важно: в документации и в сообществе встречаются разные названия и варианты конфигурации. Здесь показан практический путь, который можно расширять.
Что вам понадобится
- Node.js и npm
- Базовое приложение на Express
- Пакет express-rate-limit
Термины: rate limiting — ограничение частоты запросов; middleware — промежуточная функция Express, которая обрабатывает запросы до маршрутов.
Шаг 1: Создание окружения разработки
Создайте каталог проекта и инициализируйте npm:
mkdir express-app
Зайдите в каталог:
cd express-app
Инициализируйте package.json:
npm init -y
Установите зависимости:
npm install express express-rate-limitПояснение: express — веб-фреймворк для Node.js. express-rate-limit — middleware, ограничивающий число повторяющихся запросов к публичным API и конечным точкам.
Шаг 2: Создание простого Express-приложения
Создайте файл index.js в корне проекта и добавьте следующий код:
// index.js
const express = require("express");
const app = express();
const port = process.env.PORT || 3000
app.listen(port, () => {
console.log(`App running on port ${port}`);
}); Этот код импортирует express, создаёт приложение и запускает прослушивание на порту 3000 (или значении из переменной среды PORT).
Шаг 3: Создание обработчиков маршрутов
Создайте папку routes и файл routes/routes.js (имя файла в примере — routes.js внутри папки routes). Пример содержимого:
const express = require("express");
const router = express.Router();
router.get("/", (req, res) => {
res.send({ message: "Hello, this is a GET request" });
});
router.post("/add-demo", (req, res) => {
res.status(201).send({ message: "Resource created successfully" });
});
router.put("/update-demo", (req, res) => {
res.status(201).send({ message: "Resource updated sucessfully" });
});
module.exports = router; Затем импортируйте роутер в index.js и используйте как middleware (до app.listen):
// index.js
const routes = require("./routes/routes"); и
// index.js
app.use(routes); Убедитесь, что подключение маршрутов расположено перед вызовом app.listen.
Шаг 4: Реализация rate limiting
Создайте папку middleware и файл middleware/rate-limiter.js с таким содержимым:
// rate-limiter.js
const rateLimiter = require("express-rate-limit");
const limiter = rateLimiter({
max: 5,
windowMS: 10000, // 10 seconds
message: "You can't make any more requests at the moment. Try again later",
});
module.exports = limiter Пояснение параметров в объекте конфигурации:
- max — максимальное число запросов от одного клиента за окно. Обычно число или функция.
- windowMS — продолжительность окна в миллисекундах (ms). В примере указано 10000 (10 секунд).
- message — сообщение, отправляемое при превышении лимита. Может быть строкой, объектом JSON или любым значением, допустимым для res.send.
Поведение: при превышении лимита middleware вернёт статус 429 Too Many Requests.
Затем примените limiter глобально, подключив его в index.js выше маршрутов:
app.use(limiter); После этого все маршруты будут защищены общим лимитом.
Ограничение для отдельных маршрутов
Иногда нужно отдельное ограничение для чувствительных маршрутов (например, вход в аккаунт). В этом случае удалите глобальный limiter и экспортируйте несколько конфигураций:
const signInLimiter = rateLimiter({
max: 3,
windowMS: 10000, //10 seconds
message: "Too many sign-in attempts. Try again later."
})
module.exports = {
limiter,
signInLimiter
} Примените в роутере:
// router.js
const express = require("express");
const router = express.Router();
const {limiter, signInLimiter} = require("../middleware/rate-limiter")
router.get("/sign-in", signInLimiter, (req, res, next) => {
res.send({ message: "Hello, this is a GET request" });
});
router.use(limiter)
router.post("/post", (req, res) => {
res.status(201).send({ message: "Resource created successfully" });
});
router.put("/put", (req, res) => {
res.status(201).send({ message: "Resource updated sucessfully" });
});
module.exports = router; Таким образом для маршрута /sign-in будет действовать более строгая политика, а для остальных маршрутов — общая.
Частые ошибки и на что обратить внимание
- Параметры windowMS vs windowMs: в разных примерах используется разная капитализация. Проверьте документацию вашей версии express-rate-limit и используйте корректное имя параметра.
- Глобальный limiter накладывается на все маршруты. Уберите глобальное подключение, если хотите комбинировать настроенные лимитеры для отдельных маршрутов.
- IP-адрес как ключ: по умолчанию ключом считается IP клиента. Если у вас прокси или CDN (например, Nginx, Cloudflare), настройте trust proxy и/или используйте заголовки X-Forwarded-For.
- Хранилище счётчиков: по умолчанию используется память процесса. Это не годится для кластеров/мультисерверных развёртываний. Для продакшна используйте внешнее хранилище (Redis, Memcached).
Альтернативные подходы и когда они подходят
- Токен-ведро (token bucket): хорош для сглаживания всплесков трафика при необходимости равномерного распределения.
- Скользящее окно (sliding window): уменьшает «стаковый» эффект фиксированных окон.
- API Gateway / WAF: если у вас уже стоит API-шлюз (AWS API Gateway, Kong, Nginx, Cloudflare), имеет смысл реализовать лимиты на уровне инфраструктуры.
- CAPTCHA для ограниченных маршрутов (например, повторяющиеся попытки входа).
Когда простого express-rate-limit не хватает:
- У вас кластерная архитектура без общего хранилища — используйте Redis-backed store.
- Атакуют на уровне сетевого стека (L3/L4) — нужны сетевые фильтры и WAF.
Модель мышления и эвристики
- Начинайте с малого: разумный глобальный лимит + отдельные жёсткие лимиты для уязвимых маршрутов (вход, восстановление пароля).
- Защитите ресурсоёмкие операции более жёстко.
- Лимиты для анонимных пользователей обычно строже, чем для авторизованных.
- Пользовательский опыт важен: возвращайте понятные сообщения и рекомендации (через Retry-After заголовок).
Фактбокс — ключевые числа
- Пример в статье использует max = 5 и windowMS = 10000 (5 запросов за 10 секунд).
- Часто по умолчанию используют 5 запросов на минуту или 100–1000 запросов в минуту в зависимости от API.
- HTTP статус при превышении лимита: 429 Too Many Requests.
Безопасность и харднинг
- Используйте общую базу для счётчиков (Redis) при масштабировании.
- Установите trust proxy в Express, если запросы проходят через прокси: app.set(‘trust proxy’, 1).
- Логируйте события с превышением лимита и интегрируйте с SIEM.
- Для чувствительных маршрутов добавляйте вторую проверку (CAPTCHA, двухфакторная аутентификация).
Роли и чеклист (разработчик / SRE / безопасность)
Разработчик:
- Внедрить express-rate-limit и локальные лимитеры.
- Добавить информативные сообщения и заголовки Retry-After.
SRE / DevOps:
- Подключить Redis/Memcached как store для счётчиков.
- Настроить мониторинг и алерты на рост ошибок 429.
Специалист по безопасности:
- Анализировать логи превышений лимитов и выявлять брутфорс.
- Настроить политики на WAF и CDN.
Руководство — быстрый план действий (SOP)
- Инициализация проекта и установка зависимостей.
- Создание middleware/rate-limiter.js с базовой конфигурацией.
- Подключение limiter в index.js (локально или глобально).
- Для продакшна: подключить Redis Store и настроить trust proxy.
- Тестирование: функциональные тесты и нагрузочное тестирование.
- Деплой и мониторинг.
Тест-кейсы и критерии приёмки
- Кейс 1: Клиент делает 5 запросов в течение окна — все проходят. Шестой — возвращает 429.
- Кейс 2: Для маршрута /sign-in сделано 3 запроса в окне — четвёртый возвращает 429.
- Кейс 3: При наличии Redis-сторе счётчики синхронизируются между инстансами.
Критерии приёмки:
- Для общего набора запросов поведение соответствует конфигурации limiter.
- 429 ошибки логируются и имеют читаемое сообщение и заголовок Retry-After (если настроен).
Decision tree (Flowchart)
flowchart TD
A[Запрос пришёл] --> B{Маршрут /sign-in?}
B -- Да --> C[Применить signInLimiter]
B -- Нет --> D{Глобальный limiter подключён?}
D -- Да --> E[Применить limiter]
D -- Нет --> F[Без ограничения]
C --> G{Превышен лимит?}
E --> G
G -- Да --> H[Вернуть 429 и лог]
G -- Нет --> I[Передать обработчику маршрута]Когда ограничение не сработает или даст ложные срабатывания
- Клиенты за NAT/прокси будут концернтироваться на одном IP — возможно ложное блокирование.
- При неправильной конфигурации trust proxy — идентификация IP будет неверной.
- По умолчанию счётчики в памяти не синхронизируются между инстансами — при масштабировании поведение неконсистентно.
Миграция и совместимость
- При переходе на Redis store проверьте совместимость версии express-rate-limit и используемого store-модуля.
- Тестируйте на staging перед продакшном.
Краткое резюме
Ограничение частоты запросов в Express — недорогой и эффективный способ снизить нагрузку, уменьшить влияние ботов и защититься от ряда атак. Для простых приложений подойдёт встроенный in-memory store, но для продакшна и кластерных развёртываний рекомендуется использовать общий стор (Redis). Для чувствительных маршрутов применяйте отдельные, более строгие лимитеры.
Важно: всегда тестируйте поведение в условиях, максимально приближённых к боевым.
Глоссарий (1 строка на термин)
- Rate limiting — ограничение частоты запросов от клиента за заданный период.
- Middleware — функция, обрабатывающая запрос до передачи в маршрут.
- Store — хранилище счётчиков лимитирования (память, Redis и т.д.).
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone