req-guard-lite: Minimalny ograniczacz szybkości działania oparty na TypeScript dla Expressa
Dowiedz się, jak działa lekki limiter szybkości Express bez żadnych zależności, od domyślnych rozwiązań opartych na pamięci operacyjnej po skalowanie za pomocą Redis oraz niestandardowe generatorzy kluczy.
Każda aplikacja Express w pewnym momencie dochodzi do punktu, w którym potrzebuje ograniczeń szybkości.
Niezależnie od tego, czy celem jest ochrona tras logowania, ograniczenie spamu, czy po prostu zapobieganie niezamierzonemu nadużywaniu, ograniczanie ruchu przychodzącego szybko przestaje być opcją dodatkową i staje się koniecznością, gdy API zostanie udostępnione publicznie.
Podczas poszukiwań rozwiązania do ograniczeń szybkości, które spełniałoby powszechne wymagania, okazało się, że chociaż istnieje wiele solidnych bibliotek, wiele projektów naprawdę potrzebuje czegoś małego, łatwego do zrozumienia i prostego w dostosowaniu.
To właśnie ta luka doprowadziła do stworzenia req-guard-lite.
Dlaczego kolejny ograniczacz szybkości?
Większość API nie potrzebuje pełnego zestawu narzędzi bezpieczeństwa korporacyjnego od samego początku.
Często wystarczy umieć napisać coś w rodzaju:
app.use(rateLimit({
max: 100,
windowMs: 15 * 60 * 1000
}));
...i wróćmy do tworzenia reszty aplikacji.
Cele projektowe tego pakietu były następujące:
- Bycie lekkim
- Latwość konfiguracji
- Budowa z myślą o TypeScript od samego początku
- Możliwość łatwego rozszerzania
- Działanie zarówno w małych projektach, jak i w systemach na dużą skalę
Poznajmy req-guard-lite
req-guard-lite to kompaktowe narzędzie typu middleware dla Express, stworzone w celu ochrony Twojej API przed ogromną ilością żądań.
Domyślnie działa wyłącznie w pamięci, ale może również być skalowane do rozproszonych architektur poprzez integrację z Redis.
Jego zakres jest celowo wąski — doskonale wykonuje jedną funkcję:
Śledzi przychodzące żądania i odrzuca klientów, gdy przekroczą ustalony limit.
Funkcje
Lekka implementacja Brak zależności w czasie wykonywania w pakiecie podstawowym Działa jako middleware Express Wbudowana obsługa TypeScript Dostępna integracja z Redis Obsługa niestandardowych backendów przechowywania Obsługa niestandardowych generatorów kluczy
Pierwsze kroki
Zainstaluj go jako komponent dołączony do Express.
npm install req-guard-lite express
Następnie połącz go ze swoją aplikacją.
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);
To już cała konfiguracja.
Twoja API blokuje teraz każdego klienta, który wysyła więcej niż 100 żądań w oknie czasowym 15 minut.
Domyślne przechowywanie w pamięci
Domyślnie liczniki żądań znajdują się w pamięci.
W praktyce oznacza to:
- Nie wymaga Redis
- Nie wymaga bazy danych
- Nie potrzeba dodatkowej konfiguracji
- Idealne do rozwoju lokalnego
- Dobre rozwiązanie do produkcji na pojedynczym serwerze
Dla dużej liczby aplikacji to jest cała ograniczanie szybkości, jakiej kiedykolwiek będziesz potrzebować.
Skalowanie z Redis
Gdy aplikacja rozszerza się i działa na kilku serwerach lub kontenerach, te instancje wymagają wspólnego widoku liczby zapytań.
To właśnie pełni rolę Redis w tym przypadku.
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
})
});
Dzięki temu rozwiązaniu każda instancja serwera czyta i zapisuje te same liczniki, dzięki czemu ograniczenia pozostają spójne niezależnie od tego, który węzeł obsługuje dane zapytanie.
Spersonalizowane generatorzy kluczy
Ograniczanie szybkości na podstawie adresu IP nie zawsze jest właściwym podejściem.
W niektórych przypadkach lepiej opierać ograniczenia na:
- ID użytkownika
- Kluczu API
- Identyfikatorze najemcy
- Organizacji
- Polu podmiotu JWT
- Lub dowolnym innym identyfikatorze pasującym do twojego modelu
Aby to umożliwić, req-guard-lite pozwala na dostarczenie własnej funkcji generującej klucze.
const limiter = rateLimit({
max: 100,
keyGenerator: (req) =>
req.headers["x-api-key"] as string
});
Lub, zamiast tego, skorzystanie z klucza odziedziczonego od zalogowanego użytkownika:
const limiter = rateLimit({
max: 50,
keyGenerator: (req) =>
(req as any).user.id
});
Sam middleware nie jest świadomy tego, co reprezentuje klucz — po prostu liczy wartości zwracane przez funkcję jako identyfikator.
Weź swoją własną bazę danych
Rozszerzalność była kluczowym wymogiem od samego początku.
Zamiast zmuszać do użycia Redis, req-guard-lite oferuje prosty interfejs RateLimitStore. Jeśli twoja infrastruktura już opiera się na:
- PostgreSQL
- DynamoDB
- Memcached
- MongoDB
- SQLite
- Jakimś innym niestandardowym warstwie cache’owania
możesz to zintegrować, implementując ten jeden interfejs.
class MyStore implements RateLimitStore {
consume(key: string) {
// your implementation
}
}
Taki projekt zapewnia, że pakiet pozostaje dostosowalny do niemal każdego backendu, który już używasz.
Jeden ważny wskazówka z produkcji
Jeśli twoja aplikacja znajduje się za:
- Nginx
- AWS Load Balancerem
- Heroku
- Cloudflare
- Łącznikiem odwrotnym
upewnij się, że Express jest poprawnie skonfigurowany:
app.set("trust proxy", 1);
Jeśli pominiesz ten krok, Express często będzie traktował każdą przychodzącą prośbę tak, jakby pochodziła bezpośrednio z łącznika, co oznacza, że wszyscy twoi użytkownicy będą dzielić się tym samym limitem przepustowości. To proste rozwiązanie jedną linią kodu, ale zapobiega problemom w produkcji, które zaskakują wiele zespołów.
Jak to działa
Wewnętrzny przepływ jest celowo minimalistyczny:
- Nadchodzi prośba.
- Middleware generuje dla niej klucz (domyślnie IP klienta).
- Aktywny magazyn zwiększa licznik powiązany z tym kluczem.
Ponieważ rozwiązanie to jest modułowe, ten sam mechanizm działa niezależnie od tego, czy wykorzystuje się pamięć operacyjną, Redis, czy własną implementację.
Dlaczego TypeScript?
Ciężarówka jest napisana w języku TypeScript, co zapewnia:
- Ścisłe typowanie we wszystkich elementach
- Lepsze uzupełnianie automatyczne w edytorze
- Prostszy długoterminowy konserwacja
- API trudniejsze do niewłaściwego użycia
Użytkownicy TypeScript otrzymują pełne definicje typów od razu, bez konieczności instalowania dodatkowych pakietów @types.
Horyzont rozwoju
Rozwój trwa nadal, a kilka nowych funkcji jest już zaplanowanych.
v0.4.0
- Obsługa standardowych nagłówków odpowiedzi dotyczących ograniczeń szybkości
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
Ty nagłówki umożliwiają aplikacjom klienckim sprawdzanie, ile żądań pozostało przed osiągnięciem limitu.
v0.5.0
Dostosowalne mechanizmy, które uruchamiają się po przekroczeniu limitu i są przydatne do takich zadań jak:
- Zapisywanie logów
- Pobieranie metryk
- Wysyłanie alertów
- Analityka
- Wysyłanie danych do zewnętrznych narzędzi monitoringu
Dlaczego jest to oprogramowanie otwarte
Projekt ten nie powstał w reakcji na niewystarczające rozwiązania istniejących bibliotek — w ekosystemie Node już istnieje kilka doskonałych rozwiązań do ograniczania szybkości żądań. req-guard-lite powstał, ponieważ celem było stworzenie pakietu, który:
- Jest na tyle zwięzły, że można go przeczytać i zrozumieć od razu
- Jest łatwy do rozszerzania
- Został zaprojektowany z myślą o TypeScript
- Unika niepotrzebnej złożoności
- Jest na tyle elastyczny, by skalować się wraz z rzeczywistymi potrzebami produkcji
Budowa tego projektu była również cennym ćwiczeniem w publikowaniu pakietów, projektowaniu interfejsów API, pisaniu testów, integracji z Redis oraz tworzeniu abstrakcji przyjaznych dla innych programistów.
Uwagi końcowe
Otwarcie projektu na kod otwarty to jeden z najskuteczniejszych sposobów na doskonalenie umiejętności inżynierskich. Gdy inni programiści mogą instalować, używać i wprowadzać zmiany w twoim kodzie, jesteś zmuszony myśleć poza swoim bezpośrednim przypadkiem użycia – dokumentacja, projektowanie API, testy, wersjonowanie oraz kompatybilność wsteczna stają się rzeczywistymi ograniczeniami, które musisz uwzględnić w swoich rozwiązaniach.
req-guard-lite zaczęło się jako małe narzędzie środowiskowe stworzone, by zaspokoić osobistą potrzebę, ale ma się ono rozwijać w przydatną, lekką i rozszerzalną opcję do ograniczania szybkości żądań dla innych programistów używających Express. Wszelkie opinie, sugestie dotyczące nowych funkcji oraz wkłady są mile widziane.
Literatura pokrewna
- Dzielenie sobą jednym schema Zod między frontendem React a backendem Node — Dowiedz się, jak pojedyncze schema Zod może weryfikować formularze React, odpowiedzi API, treści żądań Express oraz zmienne środowiskowe, jednocześnie generując odpowiadające im typy TypeScript.
- Zod vs express-validator: Dwie podejścia do weryfikacji w Expressie — Porównuje weryfikację żądań opartą na schemacie z Zod z interfejsem middleware express-validator bazującym na łańcuchach, omawiając konfigurację, formatowanie błędów oraz częste pułapki.