Startseite / Artikel / req-guard-lite: Ein minimalistischer Rate-Limiter für Express, der TypeScript in den Vordergrund stellt

req-guard-lite: Ein minimalistischer Rate-Limiter für Express, der TypeScript in den Vordergrund stellt

Erfahren Sie, wie ein leichtgewichtiger Express-Rate-Limiter ohne Abhängigkeiten funktioniert – von den Standardwerten im Arbeitsspeicher über Skalierung mit Redis bis hin zu benutzerdefinierten Schlüsselgeneratoren.

1291 Wörter

Jede Express-Anwendung erreicht irgendwann den Punkt, an dem eine Rate-Limiting-Funktion erforderlich wird.

Egal, ob das Ziel darin besteht, Anmeldepfade zu schützen, Spam einzudämmen oder versehentliches Übernutzen zu verhindern – das Beschränken des einströmenden Verkehrs verwandelt sich schnell von einem wünschenswerten Feature in eine Notwendigkeit, sobald eine API der Öffentlichkeit zugänglich gemacht wird.

Beim Suchen nach einer Rate-Limiting-Lösung, die eine Reihe gängiger Anforderungen erfüllt, wurde klar, dass es zwar bereits viele leistungsstarke Bibliotheken gibt, viele Projekte jedoch eigentlich nur etwas Kleines, Einfaches und Anpassungsfähiges benötigen.

Genau diese Lücke führte zur Entwicklung von req-guard-lite.

Warum ein weiterer Rate-Limiter?

Die meisten APIs benötigen von Anfang an kein vollständiges Unternehmenssicherheitspaket.

Oft reicht es bereits aus, etwas wie Folgendes schreiben zu können:

app.use(rateLimit({
  max: 100,
  windowMs: 15 * 60 * 1000
}));

…und wir kehren zum Aufbau des Rests der Anwendung zurück.

Die Entwurfsziele für dieses Paket waren:

  • Einfach und leichtgewichtig bleiben
  • Schnell einrichtbar sein
  • Von Anfang an mit TypeScript entwickelt werden
  • Eine einfache Erweiterung ermöglichen
  • Sowohl für kleine Projekte als auch für großangelegte Systeme geeignet sein

Einführung in req-guard-lite

req-guard-lite ist ein kompaktes Express-Middleware-Modul, das entwickelt wurde, um Ihre API vor einer überwältigenden Anzahl an Anfragen zu schützen.

Standardmäßig läuft es vollständig im Speicher, kann aber auch durch Anbindung an Redis auf verteilte Architekturen skaliert werden.

Ihr Anwendungsbereich ist absichtlich eng gefasst – es erledigt eine Aufgabe hervorragend:

Es überwacht eingehende Anfragen und lehnt Clients ab, sobald diese die von Ihnen festgelegte Grenze überschreiten.

Funktionen

Leichte Implementierung Keine Laufzeitabhängigkeiten im Kernpaket Funktioniert als Express-Middleware Natives TypeScript-Unterstützung Verfügbar ist die Integration mit Redis Unterstützung für benutzerdefinierte Speicherdienste Unterstützung für benutzerdefinierte Schlüsselgeneratoren

Einführung

Installieren Sie es als Ergänzung zu Express.

npm install req-guard-lite express

Dann verbinden Sie es mit Ihrer Anwendung.

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);

Das ist die gesamte Einrichtung.

Ihre API blockiert nun jeden Client, der innerhalb eines Zeitraums von 15 Minuten mehr als 100 Anfragen sendet.

Standardmäßiger In-Memory-Speicher

Vorab sind die Anfragenzähler im Speicher gespeichert.

Praktisch bedeutet das:

  • Kein Redis erforderlich
  • Keine Datenbank erforderlich
  • Keine zusätzliche Konfiguration nötig
  • Ideal für die lokale Entwicklung
  • Gute Wahl für die Produktion auf einem einzigen Server

Für den größten Teil der Anwendungen ist das die gesamte Rate-Limiting-Funktion, die man jemals benötigen wird.

Skalierung mit Redis

Sobald eine Anwendung auf mehrere Server oder Container ausgeweitet wird, benötigen diese Instanzen einen gemeinsamen Überblick über die Anzahl der Anfragen.

Darum kümmert sich hier 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
    })
});

Dadurch liest und schreibt jede Serverinstanz die gleichen Zähler, sodass die Limits unabhängig davon konstant bleiben, welcher Knoten eine bestimmte Anfrage bearbeitet.

Kundenspezifische Schlüsselgeneratoren

Rate-Limiting nach IP-Adresse ist nicht immer der richtige Ansatz.

In einigen Fällen ist es sinnvoller, die Limits anhand folgender Elemente festzulegen:

  • Eine Benutzer-ID
  • Eine API-Schlüssel
  • Einen Mieter-Identifikator
  • Eine Organisation
  • Einen JWT-Subject-Claim
  • Oder jeden anderen Identifikator, der zu Ihrem Modell passt

Zur Unterstützung dessen ermöglicht req-guard-lite es Ihnen, Ihre eigene Schlüsselgenerierungsfunktion bereitzustellen.

const limiter = rateLimit({
    max: 100,
    keyGenerator: (req) =>
        req.headers["x-api-key"] as string
});

Oder angepasst an den angemeldeten Benutzer:

const limiter = rateLimit({
    max: 50,
    keyGenerator: (req) =>
        (req as any).user.id
});

Das Middleware-Tool selbst ist unabhängig davon, was der Schlüssel darstellt – es führt lediglich eine Zählung basierend auf dem Identifikator durch, den Ihre Funktion zurückgibt.

Ihre eigene Datenbank verwenden

Erweiterbarkeit war von Anfang an eine zentrale Anforderung.

Anstatt Sie an Redis zu binden, stellt req-guard-lite eine einfache RateLimitStore-Schnittstelle bereit. Wenn Ihre Infrastruktur bereits auf Folgendem beruht:

  • PostgreSQL
  • DynamoDB
  • Memcached
  • MongoDB
  • SQLite
  • Eine andere benutzerdefinierte Caching-Schicht

können Sie diese durch Implementierung dieser einzigen Schnittstelle einbinden.

class MyStore implements RateLimitStore {
    consume(key: string) {
        // your implementation
    }
}

Dieses Design sorgt dafür, dass das Paket an fast jeden bereits laufenden Backend-Anbieter angepasst werden kann.

Ein wichtiger Tipp für die Produktion

Falls Ihre Anwendung hinter folgenden Systemen läuft:

  • Nginx
  • Einem AWS Load Balancer
  • Heroku
  • Cloudflare
  • Jedem Reverse Proxy

sorgen Sie dafür, dass Express richtig konfiguriert ist:

app.set("trust proxy", 1);

Überspringen Sie diesen Schritt, und Express wird oft jede eingehende Anfrage so behandeln, als käme sie direkt vom Proxy selbst – dadurch teilen alle Benutzer letztendlich denselben Rate-Limit-Bereich. Es handelt sich dabei um eine einzeilige Lösung, die jedoch Probleme in der Produktion verhindert, die viele Teams unvorbereitet erwischen.

Wie es funktioniert

Der interne Ablauf ist absichtlich minimal gehalten:

  1. Eine Anfrage trifft ein.
  2. Das Middleware-Modul erzeugt dafür einen Schlüssel (standardmäßig die IP-Adresse des Clients).
  3. Der aktive Speicher erhöht den Zähler, der mit diesem Schlüssel verknüpft ist.
  • Sobald der Zähler die konfigurierte Schwellenwertgrenze überschreitet, antwortet das Middleware mit HTTP 429 Too Many Requests.
  • Falls die Grenze noch nicht erreicht wurde, wird der Anfrage unverändert weitergeleitet.
  • Weil der Store erweiterbar ist, funktioniert dieser Ablauf unabhängig davon, ob er auf Speicher, Redis oder einer benutzerdefinierten Implementierung basiert.

    Warum TypeScript?

    Die gesamte Bibliothek wurde in TypeScript geschrieben, was folgende Vorteile bringt:

    • Strenge Typisierung überall
    • Bessere Autocompletion-Funktionen im Editor
    • Einfachere Langzeitwartung
    • APIs, die schwieriger falsch verwendet werden können

    TypScript-Nutzer erhalten vollständige Typdefinitionen direkt aus der Box, ohne dass zusätzliche @types-Pakete installiert werden müssen.

    Roadmap

    Die Entwicklung ist weiterhin im Gange, und bereits einige Funktionen sind geplant.

    v0.4.0

    • Unterstützung für standardmäßige Rate-Limit-Response-Header
    X-RateLimit-Limit
    X-RateLimit-Remaining
    X-RateLimit-Reset
    

    Diese Header geben Client-Anwendungen Aufschluss darüber, wie viele Anfragen noch möglich sind, bevor die Obergrenze erreicht wird.

    v0.5.0

    Einstellbare Hooks, die ausgelöst werden, wenn eine Grenze überschritten wird – nützlich für Dinge wie:

    • Protokollierung
    • Sammlung von Metriken
    • Alarmierung
    • Analysen
    • Sendung von Daten an externe Überwachungstools

    Warum es Open Source ist

    Dieses Projekt entstand nicht als Reaktion darauf, dass bestehende Bibliotheken unzureichend sind – es gibt bereits mehrere hervorragende Lösungen für das Rate-Limiting im Node-Ecosystem. req-guard-lite existiert, weil das Ziel ein Paket war, das:

    • so kompakt ist, dass es auf einen Blick gelesen und verstanden werden kann
    • einfach erweiterbar ist
    • in erster Linie für TypeScript konzipiert wurde
    • keine unnötige Komplexität aufweist
    • flexibel genug ist, um mit den tatsächlichen Anforderungen in der Produktion mitzuwachsen

    Die Entwicklung war zudem eine wertvolle Übung im Veröffentlichen von Paketen, Entwerfen von APIs, Schreiben von Tests, Integration mit Redis sowie in der Gestaltung solcher Abstraktionen, die für andere Entwickler leicht zu nutzen sind.

    Fazit

    Das Open-Source-Machen eines Projekts ist eine der effektivsten Methoden, um eigene Ingenieursfähigkeiten zu schärfen. Sobald andere Entwickler Ihren Code installieren, nutzen und dazu beitragen können, sind Sie gezwungen, über Ihren eigenen unmittelbaren Anwendungsfall hinauszudenken – Dokumentation, API-Design, Testing, Versionierung und Rückwärtskompatibilität werden zu echten Einschränkungen, um die Sie sich kümmern müssen.

    req-guard-lite entstand ursprünglich als kleines Middleware-Tool, um einen persönlichen Bedarf zu erfüllen, doch es soll sich zu einer nützlichen, leichten und erweiterbaren Option zur Rate-Limiting-Funktion für andere Express-Entwickler entwickeln. Feedback, Vorschläge zu neuen Funktionen sowie Beiträge sind jederzeit willkommen.

    Weitere Literatur