Skip to content

Migrating

Ameya Borkar edited this page May 26, 2026 · 2 revisions

Migrating

Drop-in paths from the two most common Node rate limiters, plus a few recipes.

From express-rate-limit

// before
app.use(rateLimit({ windowMs: 60_000, limit: 100 }));
// after — GCRA by default (smooth pacing, no 2× boundary burst), same standards headers
import { expressRateLimit } from "throttlekit/express";
import { gcra } from "throttlekit";
app.use(expressRateLimit({ strategy: gcra({ limit: 100, periodMs: 60_000 }) }));
// want the classic window? swap in fixedWindow({ limit: 100, windowMs: 60_000 })

From rate-limiter-flexible

// before — throws on exhaustion
try { await rl.consume(key); } catch { /* respond 429 */ }
// after — one atomic Lua round trip, a Decision object instead of throw-on-deny
import { rateLimit, gcra } from "throttlekit";
import { RedisStore } from "throttlekit/redis";
const limiter = rateLimit({ strategy: gcra({ limit: 100, periodMs: 60_000 }), store: new RedisStore({ client: redis }) });
const d = await limiter.check(key);
if (!d.allowed) { /* respond 429 with d.retryAfterMs */ }

The main shape change is throw-on-deny → a Decision object: instead of try/catch, branch on d.allowed and read d.retryAfterMs / d.remaining / d.resetAt.

Recipes

// Tiered plans (free / pro) by API key — one store, namespaced per tier
const limiters = {
  free: rateLimit({ strategy: gcra({ limit: 60, periodMs: 60_000 }), store, prefix: "free" }),
  pro:  rateLimit({ strategy: gcra({ limit: 1_000, periodMs: 60_000 }), store, prefix: "pro" }),
};
const d = await limiters[planFor(req)].check(apiKeyOf(req));

// Cost-weighted endpoints — charge expensive routes more from the same budget
await limiter.check(apiKeyOf(req), routeIsExpensive(req) ? 5 : 1);
  • Per-IP and per-route in one round trip — see Advanced limiting.
  • Tiered burst + sustained — compose two GCRA limiters (e.g. 10/sec and 1000/hour) and allow only if both pass.
  • Global limit across regionstwoTier leased at a shared store; see Distributed & provable.

Clone this wiki locally