req-guard-lite: Мінімальний лімітер швидкості на основі TypeScript для Express
Дізнайтеся, як працює легкий обмежувач швидкості Express без залежностей, від стандартних налаштувань у пам’яті до масштабування за допомогою Redis та користувацьких генераторів ключів.
Кожен додаток Express рано чи пізно доходить до моменту, коли потрібне обмеження частоти запитів.
Чи йдеться про захист маршрутів входу, зменшення кількості спаму чи просто запобігання ненавмисному надмірному використанню, обмеження надходження трафіку швидко перетворюється з бажаної функції на обов’язкову, як тільки API стає доступним для публічного використання.
Під час пошуку рішення для обмеження частоти запитів, яке відповідало б поширеним потребам, стало зрозуміло, що хоча існує багато потужних бібліотек, багато проектів насправді хочуть щось просте, зрозуміле та легке для налаштування.
Саме ця прогалина призвела до створення req-guard-lite.
Чому ще один обмежувач частоти?
Більшості API не потрібен повний корпоративний набір засобів безпеки вже з першого дня.
Часто все, що потрібно, — це можливість написати щось на кшталт:
app.use(rateLimit({
max: 100,
windowMs: 15 * 60 * 1000
}));
...і повернемося до створення решти додатку.
Цілі проектування цього пакета були наступними:
- Залишатися легким у використанні
- Бути простим у налаштуванні
- Бути створеним з використанням TypeScript з самого початку
- Дозволяти легке розширення
- Функціонувати як у невеликих проектах, так і в великих системах
Представляємо req-guard-lite
req-guard-lite — це компактний середовий модуль Express, створений для захисту вашого API від надмірної кількості запитів.
За замовчуванням він працює повністю в пам’яті, але також може масштабуватися до розподілених систем шляхом підключення до Redis.
Його функціонал навмисно обмежений — він добре виконує одну роль:
Відстежує надходження запитів та відхиляє клієнтів, як тільки вони перевищують встановлений ліміт.
Функції
Легка реалізація Відсутність залежностей під час виконання у основному пакеті Функціонує як мідлвейр Express Підтримка TypeScript з першого дня Можлива інтеграція з Redis Підтримка користувацьких бекендів зберігання Підтримка користувацьких генераторів ключів
Початок роботи
Встановіть його як доповнення до Express.
npm install req-guard-lite express
Далі під’єднайте його до свого додатку.
import express from 'express';
import { rateLimit } from 'req-guard-lite';
const app = express();
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Too many requests, please try again later.'
});
app.use(limiter);
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000);
Це і є вся налаштування.
Тепер ваш API блокує будь-якого клієнта, який надсилає більше 100 запитів протягом 15 хвилин.
Стандартний внутрішній сховище
За замовчуванням лічильники запитів зберігаються в пам’яті.
На практиці це означає:
- Не потрібен Redis
- Не потрібна база даних
- Немає потреби у додатковій налаштуванні
- Ідеально підходить для локального розробки
- Хороший вибір для роботи у продакшені на одному сервері
Для більшості додатків це все, що потрібно для обмеження швидкості.
Масштабування за допомогою Redis
Коли додаток починає працювати на кількох серверах чи контейнерах, цим екземплярам потрібен спільний погляд на кількість запитів.
Саме цю роль виконує Redis.
import Redis from "ioredis";
import { createRedisStore } from "req-guard-lite/redis";
const redis = new Redis();
const limiter = rateLimit({
max: 100,
windowMs: 15 * 60 * 1000,
store: createRedisStore(redis, {
max: 100,
windowMs: 15 * 60 * 1000
})
});
Завдяки цьому кожен екземпляр сервера читає та записує однакові лічильники, тож обмеження залишаються послідовними, незалежно від того, який вузол обробляє певний запит.
Складні генератори ключів
Обмеження швидкості за IP-адресою не завжди є правильним підходом.
У деяких випадках краще базувати обмеження на:
- ID користувача
- Ключі API
- Ідентифікатор орендаря
- Організації
- Запереченні суб’єкта JWT
- Або будь-якому іншому ідентифікаторі, який підходить вашій моделі
Щоб підтримати це, req-guard-lite дозволяє вам надати власну функцію генерації ключа.
const limiter = rateLimit({
max: 100,
keyGenerator: (req) =>
req.headers["x-api-key"] as string
});
Або, замість цього, використовувати ідентифікатор увійшого користувача:
const limiter = rateLimit({
max: 50,
keyGenerator: (req) =>
(req as any).user.id
});
Сам мідлвейр не залежить від того, що представляє ключ — він просто підраховує кількість операцій за будь-яким ідентифікатором, який повертає ваша функція.
Використовуйте власний сховище
Можливість розширення була основною вимогою з самого початку.
Замість того, щоб примушувати вас використовувати Redis, req-guard-lite надає простий інтерфейс RateLimitStore. Якщо ваша інфраструктура вже базується на:
- PostgreSQL
- DynamoDB
- Memcached
- MongoDB
- SQLite
- Якомусь іншому користувацькому рівні кешування
ви можете його інтегрувати, реалізувавши цей єдиний інтерфейс.
class MyStore implements RateLimitStore {
consume(key: string) {
// your implementation
}
}
Ця архітектура забезпечує можливість адаптації пакета майже до будь-якого бекенду, який ви вже використовуєте.
Один важливий порадник для продакшну
Якщо ваш додаток розташований за:
- Nginx
- AWS Load Balancer
- Heroku
- Cloudflare
- Будь-яким зворотним проксі
обов’язково налаштуйте Express правильно:
app.set("trust proxy", 1);
Якщо пропустити цей крок, Express часто буде розглядати кожен надійшлий запит так, ніби він походить безпосередньо від проксі, що означає, що всі ваші користувачі будуть ділитися одним і тим самим лімітом швидкості. Це можна виправити однією рядком коду, але це уникне проблем у продакшні, які несподівано трапляються у багатьох командах.
Як це працює
Внутрішній процес спеціально зроблений мінімалістичним:
- Надходить запит.
- Мідлверк генерує для нього ключ (за замовчуванням — IP-адреса клієнта).
- Активний сховище збільшує лічильник, пов’язаний із цим ключем.
Оскільки цей механізм є модульним, той самий алгоритм працює незалежно від того, чи використовується пам’ять, Redis чи власна реалізація.
Чому TypeScript?
Уся бібліотека написана на TypeScript, що забезпечує:
- Сувору типізацію в усьому коді
- Краще автодоповнення в редакторі
- Простіше довгострокове обслуговування
- API, які важче зловживати
Користувачі TypeScript отримують повні визначення типів без додаткових пакетів @types для встановлення.
Плани розвитку
Розробка триває, і вже заплановано кілька функцій.
v0.4.0
- Підтримка стандартних заголовків відповіді для обмеження частоти запитів
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
Ці заголовки надають клієнтським додаткам можливість бачити, скільки запитів залишилося до досягнення ліміту.
v0.5.0
Налаштовувані функції, які активуються при перевищенні ліміту, що корисно для таких завдань, як:
- Логування
- Збір метрик
- Надсилання сповіщень
- Аналітика
- Надсилання даних до зовнішніх інструментів моніторингу
Чому це відкритий код
Цей проект не є реакцією на недоліки існуючих бібліотек — у екосистемі Node вже існує кілька чудових рішень для обмеження швидкості. req-guard-lite існує тому, що метою був пакет, який:
- Достатньо компактний, щоб можна було прочитати та зрозуміти його за один раз
- Легкий у розширенні
- Розроблений з урахуванням TypeScript на першому місці
- Позбавлений зайвої складності
- Достатньо гнучкий, щоб масштабуватися відповідно до реальних потреб у продакшені
Створення цього проекту також стало корисною практикою у публікації пакетів, проектуванні API, написанні тестів, інтеграції з Redis та формуванні абстракцій, які зручні для використання іншими розробниками.
Заключні зауваження
Розголошення проекту під ліцензією open-source — це один із найефективніших способів вдосконалення ваших інженерних навичок. Коли інші розробники можуть встановлювати, використовувати та робити внески у ваш код, ви змушені думати не лише про свої безпосередні потреби — документація, проектування API, тестування, версіонування та зворотна сумісність стають реальними обмеженнями, навколо яких потрібно будувати рішення.
req-guard-lite починався як невеликий мідлвейр, створений для вирішення особистої проблеми, але сподіваються, що він перетвориться на корисний, легкий та розширюваний інструмент обмеження кількості запитів для інших розробників Express. Будемо вдячні за ваші відгуки, пропозиції щодо функцій та внески.
Пов’язана література
- Розподіл однієї схеми Zod між вашим React-фронтендом та Node-бекендом — Дізнайтеся, як одна схема Zod може перевіряти форми React, відповіді API, тіла запитів Express та змінні середовища, водночас генеруючи відповідні типи TypeScript.
- Zod проти express-validator: два підходи до верифікації даних у Express — Порівнює верифікацію запитів на основі схеми з використанням Zod та середовища express-validator, що ґрунтується на ланцюгах, розглядаючи налаштування, форматування помилок та поширені проблеми.