PAS7 Studio

Rate Limiting in Bun.js: In-Memory, Redis, Sliding Window und Edge Cases für Production APIs

Ein praktischer Deep Dive zu Rate Limiting Middleware in Bun.js: Fixed Window, Sliding Window, Token Bucket, Redis, Distributed Limits, 429 Retry-After, Abuse Protection, Hono/Elysia Integrationen, Best Practices und schlechte Praktiken.

14. Mai 2026· 14 Min. Lesezeit· Technologie
Geeignet fürBackend EngineersFull-Stack-EntwicklerTech LeadsTeams, die Public APIs, SaaS oder Webhook Endpoints auf Bun bauen
Technische Illustration einer Rate-Limiting-Pipeline in Bun.js mit Counters, Redis und kontrolliertem Request Flow

Ein naives Limit wie „100 Requests pro IP pro Minute“ sieht genau bis zum ersten Büro hinter NAT, dem ersten Mobilfunkanbieter, dem ersten Webhook Retry Storm oder dem ersten Enterprise Tenant mit hunderten Nutzern hinter einer Egress IP gut aus.

Rate Limiting muss nicht nur „wie viele Requests?“ beantworten, sondern auch: „wer wird limitiert?“, „welche Route?“, „welcher Tenant?“, „welches Credential?“, „was passiert, wenn Redis nicht verfügbar ist?“, „gibt es Retry-After?“ und „brechen wir einen normalen Burst?“.

Bun macht den HTTP Layer schnell, aber Rate Limiting hängt immer an Key Design, Storage Atomicity und Failure Policy. Genau das zerlegen wir hier.

Ein In-Memory Limiter passt nur für einen Prozess oder einen lokalen Baseline Guard.
Redis wird nötig, wenn die API mehrere Instanzen oder horizontale Skalierung hat.
Sliding Window und Token Bucket lösen unterschiedliche Probleme.
429 ohne Retry-After macht guten Clients das Leben schwerer.

Das ist das dritte Chapter der Bun Middleware Serie. Nach Overview und Auth ist Abuse Protection der logische nächste Schritt: Rate Limiting steht oft zwischen deiner API und einer teuren Welle unnötiger Requests.

Das mentale Modell von Rate Limiting Middleware: identify key, choose policy, check counter, allow oder reject.
Fixed Window, Sliding Window und Token Bucket: wo jeder Algorithmus gut funktioniert und wo Edge Cases entstehen. [5][6]
Ein nativer Bun In-Memory Limiter für einen Prozess.
Redis-backed Limiter für Distributed APIs und warum Atomicity wichtig ist. [5][6]
Hono/Elysia Varianten: wann ready-made Middleware/Plugin sinnvoll ist. [1][2]
HTTP 429 Too Many Requests und Retry-After: was der Client bekommen sollte. [7]
Schlechte Praktiken: IP-only Limits, globale Limits für alle Routes, fail-open ohne Alerts, Plaintext API Key im Limiter Key.

Ein Rate Limiter sollte eine kurze State Machine sein, nicht eine zufällige Prüfung mitten im Handler. Wenn man ihn in Schritte aufteilt, werden die meisten Fehler vor dem Code sichtbar.

01

Identity Key bestimmen

Das kann user id, API key id, tenant id, route group, IP oder eine Kombination sein. Für einen authenticated API key ist apiKeyId + routeGroup meistens besser als nur IP.

02

Policy wählen

Unterschiedliche Routes brauchen unterschiedliche Limits: login, search, export, webhook, public read, admin mutation. Ein globales Limit ist fast immer entweder zu schwach oder zu aggressiv.

03

Counter atomar aktualisieren

In einem Prozess ist das eine Map. In einer Distributed API ist es Redis oder ein anderer Shared Store. Für Redis müssen increment + expiry oder Sliding-Window-Update atomar sein.

04

Nützliches 429 zurückgeben

Der Client sollte einen stabilen JSON Error, Retry-After und idealerweise Rate-Limit-Headers bekommen. Sonst wissen gute Clients nicht, wann sie wiederholen sollen.

05

Ohne Secrets loggen

Logge limiter key hash/prefix, route group, tenant, decision, remaining und reset time. Logge keinen vollständigen API Key oder Authorization Header.

Kurz gesagt

Wenn du keine explizite Antwort auf jeden dieser Schritte hast, ist der Limiter noch nicht production-ready.

Der Algorithmus bestimmt nicht nur Genauigkeit, sondern auch UX. Zwei Clients können gleich viele Requests pro Minute haben, aber einer erzeugt einen Burst an der Fenstergrenze, der andere verteilt gleichmäßig.

AlgorithmusWie er funktioniertWann geeignetSchwachstelle
Fixed windowCounter für ein fixes Fenster, zum Beispiel 100 Requests pro MinuteEinfache internal endpoints, low-risk APIs, günstiger BaselineBoundary Burst: Client kann viele Requests an der Grenze zweier Fenster senden
Sliding window logSpeichert Timestamps von Requests und zählt nur solche im beweglichen FensterKritische APIs, Login, Checkout, teure OperationsMehr Storage und Cleanup Work pro Request
Sliding window counterApproximiert ein bewegliches Fenster über aktuelles und vorheriges Bucket-FensterBalance aus Genauigkeit und Kosten für High-Traffic APIsWeniger genau als Log-Variante, braucht vorsichtige Reset-Mathematik
Token bucketClient hat einen Bucket aus Tokens, der über Zeit aufgefüllt wirdAPIs, bei denen ein kurzer Burst normal ist, aber die durchschnittliche Rate kontrolliert sein mussCapacity und Refill Rate müssen richtig gewählt werden

Fixed Window ist simpel, Sliding Window genauer, Token Bucket toleriert legitime Bursts besser.

Screenshot des Abschnitts algorithm-choice

Kurz gesagt

Starte mit Fixed Window für einen einfachen Baseline, aber für teure öffentliche Routes sind Sliding Window oder Token Bucket meist besser.

Für einen Bun Prozess kann man einen einfachen In-Memory Fixed-Window Limiter schreiben. Er ist nützlich für local dev, internal tools, single-instance deployments oder als fallback, synchronisiert sich aber nicht zwischen Instanzen.

Minimaler Beispielcode:

TS
type LimitEntry = { count: number; resetAt: number };
const limits = new Map<string, LimitEntry>();

const WINDOW_MS = 60_000;
const MAX_REQUESTS = 120;

function rateLimitKey(req: Request) {
  const apiKeyId = req.headers.get("x-api-key-id");
  const ip = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "unknown";
  return apiKeyId ? `api:${apiKeyId}` : `ip:${ip}`;
}

function checkLimit(key: string, now = Date.now()) {
  const current = limits.get(key);

  if (!current || current.resetAt <= now) {
    limits.set(key, { count: 1, resetAt: now + WINDOW_MS });
    return { allowed: true, remaining: MAX_REQUESTS - 1, resetAt: now + WINDOW_MS };
  }

  if (current.count >= MAX_REQUESTS) {
    return { allowed: false, remaining: 0, resetAt: current.resetAt };
  }

  current.count += 1;
  return { allowed: true, remaining: MAX_REQUESTS - current.count, resetAt: current.resetAt };
}

Bun.serve({
  async fetch(req) {
    const decision = checkLimit(rateLimitKey(req));

    if (!decision.allowed) {
      const retryAfter = Math.ceil((decision.resetAt - Date.now()) / 1000);
      return Response.json(
        { error: "rate_limited", retryAfter },
        { status: 429, headers: { "Retry-After": String(retryAfter) } },
      );
    }

    return Response.json({ ok: true, remaining: decision.remaining });
  },
});

Das ist ein Fixed-Window Baseline. Er räumt alte Keys nicht aktiv auf, funktioniert nicht über mehrere Prozesse hinweg, hat keine tenant-specific policies und schützt nicht vor Boundary Bursts. Aber er zeigt die richtige Form: key, counter, decision, 429, Retry-After.

Wo das sinnvoll ist

Ein In-Memory Limiter passt für einen Prozess oder als günstiger lokaler Guard. Eine Production API mit Autoscaling braucht einen Shared Store.

Sobald eine Bun API in mehreren Instanzen läuft, sind In-Memory Counters kein globales Limit mehr. Ein Client kann Requests über Instanzen verteilen und bekommt einen Multiplikator auf sein Limit. Redis löst das als Shared Counter Store.

Das kritische Detail: Counter Update muss atomar sein. Für Fixed Window ist das oft INCR + EXPIRE, aber expiry muss beim ersten Increment korrekt gesetzt werden. Für Sliding Window Log nutzt man häufig Sorted Sets und Cleanup alter Timestamps. Für Token Bucket braucht man oft ein Lua Script oder einen anderen atomaren Mechanismus, der refill und consume in einer Operation berechnet. Redis Rate Limiting Patterns basieren typischerweise auf atomaren Counters oder Lua. [5][6]

Redis fügt außerdem einen Failure Mode hinzu: Was passiert, wenn Redis nicht verfügbar ist? Für teure public routes ist fail closed oder degraded limit oft sicherer. Für eine kritische interne Control Plane kann eine andere Policy gelten. Aber jedes fail-open braucht Alerts, weil der Rate Limiter sonst genau dann verschwindet, wenn er am meisten gebraucht wird.

Für mehrere Bun Instanzen muss das Limit in einem Shared Store leben. Sonst gibt jede Instanz dem Client eine eigene Allowance.

Screenshot des Abschnitts redis-distributed

Praktischer Baseline

Ein Distributed Limiter braucht Shared Store, atomaren Update, Key Design, TTL Cleanup, Latency Budget und Fail Policy.

Fertige Middleware oder ein Plugin ist nützlich, wenn die Aufgabe typisch ist. Aber Key Strategy, Tenant Policy und Distributed Storage löst es nicht für dich.

Das Hono Ecosystem hat Rate Limiter Middleware mit konfigurierbarem Window, Limit, Key Generator und Store Options. Das ist gut für einen schnellen Baseline in einer Hono App. [1]
Hono rate limiter middleware
Das Elysia Ecosystem hat ein Rate-Limit Plugin für Bun-first Elysia Apps. Es passt natürlich in das Lifecycle-/Plugin-Modell von Elysia. [2]
Elysia rate-limit plugin
OWASP API Security Top 10 führt unrestricted resource consumption als eigenes Risiko. Rate Limiting sollte CPU, Memory, Storage, Network und Downstream Resources schützen. [3]
OWASP API Security

Kurz gesagt

Ein fertiger Limiter reduziert Boilerplate. Production-Qualität bestimmen Keys, Storage, Policies, Observability und Edge Cases.

Der Rate Limit Key ist die wichtigste Entscheidung. Wenn der Key falsch ist, rettet der Algorithmus nicht mehr. IP-only Limits bestrafen oft normale Nutzer hinter NAT und erkennen authenticated abuse schlecht.

Für öffentliche anonyme Routes kann IP ein Startpunkt sein. Für authenticated APIs ist es besser, nach subject id, API key id, tenant id oder einer Kombination wie tenantId + routeGroup zu limitieren. Für Login Flows braucht man manchmal gleichzeitig IP Limit, Account/Email Limit und Device Fingerprint Policy.

Für Multi-Tenant SaaS kann ein reines User-Limit zu weich sein, weil ein Tenant mit vielen Usern eine Ressource überlasten kann. Ein reines Tenant-Limit kann zu hart sein, weil ein noisy user die ganze Firma blockiert. Oft braucht man eine Hierarchie: per-user, per-tenant, per-route und global emergency limit.

Public read: IP + route group.
Login: IP + account/email + device/risk signal.
API key: key id + route group + tenant.
SaaS tenant: tenant budget + per-user budget.
Expensive exports/search: eigenes enges Route Limit.

HTTP 429 Too Many Requests bedeutet, dass der Nutzer in einem bestimmten Zeitraum zu viele Requests gesendet hat. MDN beschreibt, dass die Response Retry-After enthalten kann, um mitzuteilen, wie lange der Client vor einem Retry warten soll. [7]

In einer Production API zwingt 429 ohne Retry-After gute Clients zum Raten. Sie wiederholen entweder zu schnell oder nutzen Exponential Backoff, obwohl sie einfach bis zum Reset warten könnten. Das verschlechtert UX und erhöht unnötige Last.

Neben Retry-After fügen viele APIs Rate-Limit-Headers hinzu, zum Beispiel RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset oder eigene X-RateLimit-*. Wichtig sind stabiler Contract und Dokumentation für Clients.

429 sollte nützlich sein: Der Client sollte wissen, wann er retryen kann, statt Backoff zu raten.

Screenshot des Abschnitts headers-contract

Kurz gesagt

Ein Rate Limiter sollte für den Client verständlich sein. 429 ohne Retry Contract erzeugt wiederholten Traffic und schlechte Integration.

Rate Limiting hat viele unangenehme Details, die im Happy Path unsichtbar sind. Unten sind die, die nach Launch am häufigsten auftauchen.

NAT und Corporate Networks

Ein IP-only Limit kann dutzende normale Nutzer hinter einer Egress IP blockieren. Für authenticated APIs besser nach subject/API key/tenant keyen.

Webhook Retry Storms

Ein Partner kann failed Webhooks ehrlich retryen und in den Limiter laufen. Webhooks brauchen eigene Policies, Idempotency und retry-aware Contract.

Clock Skew

Wenn der Limiter über verschiedene Systeme verteilt ist, müssen reset time und sliding windows stabil berechnet werden. Redis Server Time oder ein zentraler Store sind oft zuverlässiger als lokale Clocks.

Burst nach Deploy

Nach Downtime oder Deploy können Clients synchron Requests wiederholen. Token Bucket oder queued backoff kann besser sein als ein hartes Fixed Window.

Admin und Internal Routes

Gib internen Tools nicht standardmäßig unbegrenzten Zugriff. Sie starten oft die schwersten Exports und Batch Operations.

Redis Failure

Fail-open ohne Alerts macht Schutz unsichtbar. Fail-closed ohne Degradation kann das Produkt lahmlegen. Policy sollte pro Route Class unterschiedlich sein.

Diese Fehler sind nicht einzigartig für Bun, aber in Bun APIs verstecken sie sich oft hinter schnellem Runtime und einfachem Middleware Wrapper.

Ein globales Limit für alle Routes.

IP-only Limit für authenticated APIs.

In-Memory Limiter in Multi-Instance Production.

Redis INCR ohne korrektes TTL oder Atomicity.

Kein Retry-After in der 429 Response.

Limits berücksichtigen Tenant, API Key oder Route Cost nicht.

Vollständiger API Key oder Authorization Header landet als Limiter Key in Logs.

Fail-open bei Redis Outage ohne Alerting.

Rate Limiter steht nach Body Parsing für teure Payload Routes.

Keine Tests für Boundary Burst, Reset, Redis Failure und Concurrent Requests.

Review-Regel

Der Rate Limiter sollte früh stehen, den richtigen Key haben, einen atomaren Counter, klares 429 und observable decision.

Vor dem Start von Rate Limiting in Staging oder Production sollte diese Liste geprüft werden. Sie hilft, Probleme zu finden, bevor Kunden sie finden.

Route Classes sind definiert

Public, auth, login, webhook, export, admin und internal routes haben unterschiedliche Policies.

Key Strategy ist nicht IP-only

Für authenticated routes subject/API key/tenant/route group nutzen und IP als zusätzliches Signal behalten.

Distributed Store existiert für Multi-Instance

Wenn die Bun API mehrere Instanzen hat, leben Counters in Redis oder einem anderen Shared Store.

Operationen sind atomar

Increment, expiry, sliding window cleanup oder token consume laufen ohne Race Conditions.

429 hat Retry Contract

Response enthält stabilen JSON Error und Retry-After; Rate-Limit-Headers sind dokumentiert.

Limiter steht vor teuren Operationen

Rate Check passiert vor Body Parsing, DB Calls, Remote Calls und schweren Transforms, wenn die Route es erlaubt.

Redis Failure Policy ist definiert

Für jede Route Class ist bekannt, ob fail-open oder fail-closed gilt, und Alerting existiert.

Observability existiert

Decision, route group, limiter key hash, remaining, reset time, storage latency und Redis failures werden geloggt.

Bun gibt dir einen schnellen HTTP Runtime, aber Rate Limiting ist kein Runtime Feature, das man mit einer Zeile hinzufügt und vergisst. Es ist eine Security- und Reliability-Policy, die wissen muss, wen sie limitiert, für welche Route, mit welchem Storage, welchem Algorithmus und welchem Retry Contract.

Für einen Prozess kann ein In-Memory Fixed Window ein guter Baseline sein. Für Production mit mehreren Instanzen braucht man Redis oder einen anderen Shared Store. Für user-facing APIs ist Fixed Window oft zu grob; Sliding Window oder Token Bucket geben bessere UX.

Am wichtigsten: Bestrafe normale Nutzer nicht durch einen schlechten Key. IP-only Limit, ein globales Limit und 429 ohne Retry-After erzeugen meistens mehr Probleme, als sie lösen.

Reicht ein In-Memory Rate Limiter in Bun.js?

Nur für einen Prozess, local dev, internal tools oder einen einfachen Baseline. Wenn die API mehrere Instanzen hat, multipliziert sich ein In-Memory Limit mit der Anzahl der Instanzen und ist kein globaler Schutz.

Was ist besser: Fixed Window, Sliding Window oder Token Bucket?

Fixed Window ist am einfachsten, hat aber Boundary Bursts. Sliding Window ist genauer für kritische Routes, aber teurer. Token Bucket erlaubt kurze legitime Bursts und kontrolliert gleichzeitig die durchschnittliche Rate.

Warum ist IP-only Rate Limiting schlecht?

Ein IP-only Limit kann normale Nutzer hinter NAT, Corporate Proxy oder Mobilfunkanbieter blockieren und gleichzeitig schlecht gegen authenticated abuse funktionieren. Für APIs ist es besser, nach subject, API key, tenant und route group zu limitieren.

Was sollte eine Bun API bei überschrittenem Limit zurückgeben?

Einen stabilen JSON Error mit HTTP `429`, den Header `Retry-After` und idealerweise Rate-Limit-Headers wie remaining/reset. Das hilft guten Clients, korrekt zu retryen. [7]

Braucht man Redis für Rate Limiting?

Für einen Prozess nicht zwingend. Für Production mit mehreren Instanzen oder serverless/concurrent deployment ist Redis oder ein anderer Shared Store praktisch nötig, damit Counters gemeinsam sind.

Was tun, wenn Redis nicht verfügbar ist?

Fail Policy vorher definieren. Für teure public routes ist fail closed oder ein degraded strict local limit oft sicherer. Für einzelne internal/control-plane routes kann fail-open möglich sein, aber nur mit Alerting und Audit.

Diese Quellen bestätigen fertige Middleware-/Plugin-Optionen, Security Rationale für Resource Limiting, Redis Rate Limiting Patterns und HTTP Semantics für 429.

Geprüft: 14. Mai 2026Gilt für: Bun 1.3.xGilt für: Bun.serve routesGilt für: Hono 4.xGilt für: Elysia 1.xGilt für: Redis-backed APIsGetestet mit: Bun.serve middleware compositionGetestet mit: Redis countersGetestet mit: Hono rate limiter middlewareGetestet mit: Elysia rate-limit pluginGetestet mit: HTTP 429 Retry-After response

PAS7 Studio kann helfen, Rate Limiting für Bun, Hono oder Elysia zu entwerfen: route classes, Redis store, sliding window oder token bucket, API-key/tenant budgets, abuse monitoring und korrekter 429 contract.

Das ist besonders nützlich für SaaS, Public APIs, Webhook Endpoints, AI/Automation-Produkte und Migrationen von Express/Fastify, bei denen alte Limits Tenant, API Keys oder Horizontal Scaling nicht berücksichtigen.

Sie sind hier03/05

Rate Limiting in Bun.js: in-memory, Redis, Sliding Window und Edge Cases

Verwandte Artikel

ai-assistants

AI Assistant Entwicklung Kosten 2026: RAG, Knowledge Base, Integrationen und Support

Praktischer Leitfaden zu Kosten fuer AI Assistants: RAG, Knowledge Base, Channels, Tool Use, Guardrails, Evaluations, Monitoring und Support.

blogs

KI fur Landingpage-Entwicklung: wo sie Launches beschleunigt und wo sie Conversion schadet

Eine praxisnahe Analyse zur Nutzung von KI fur Landingpages: v0, Webflow AI, Builder.io, Framer-ahnliche Builder, UX-Generierung, Copy, SEO, Personalisierung, A/B-Tests, Template-Risiken, Accessibility, Security und technischer Schuldenaufbau.

growth

AI SEO / GEO im Jahr 2026: Ihre nächsten Kunden sind nicht Menschen — sondern Agents

Suche verschiebt sich von Klicks zu Antworten. Bots und AI-Agents crawlen, zitieren, empfehlen — und kaufen zunehmend. Erfahren Sie, was AI SEO / GEO bedeutet, warum klassisches SEO nicht mehr reicht und wie PAS7 Studio Marken im agentischen Web sichtbar macht.

blogs

Der leistungsstärkste Chip von Apple? M5 Pro und M5 Max brechen Rekorde

Eine Analyse zu Apple M5 Pro und M5 Max im März 2026. Wir zeigen, warum diese Chips als die stärksten professionellen Laptop-SoCs von Apple gelten können, wie sie sich gegen M4 Pro, M4 Max, M1 Pro, M1 Max schlagen und was der Vergleich mit aktuellen Intel- und AMD-Chips zeigt.

Professionelle Entwicklung für Ihr Geschäft

Wir erstellen moderne Web-Lösungen und Bots für Unternehmen. Erfahren Sie, wie wir Ihnen helfen können, Ihre Ziele zu erreichen.