A distributed rate limiting library for Node.js and TypeScript — five algorithms, two storage backends, and an Express middleware, all sharing the same pluggable interfaces so any algorithm works with any storage backend without modification.
- Algorithms: fixed window, sliding window log, sliding window counter, token bucket, leaky bucket
- Storage: in-memory (single process) or Redis (distributed, via atomic Lua scripts)
- Middleware: drop-in Express integration with
X-RateLimit-*and IETF-draftRateLimit-*headers
This project was built phase-by-phase as a teaching tool first, a library second — every
algorithm and storage backend has a companion docs/phase-N/LEARN.md explaining the concept from
first principles, and an ARCHITECTURE.md with the design decisions and trade-offs behind it. See
docs/ for the full write-ups.
npm install @iuashrafi/rate-limiterexpress and ioredis are optional peer dependencies — only install them if you're using the
Express middleware and/or RedisStore, respectively:
npm install express # only if using rateLimit()
npm install ioredis # only if using RedisStoreimport { MemoryStore, TokenBucketRateLimiter } from '@iuashrafi/rate-limiter';
const limiter = new TokenBucketRateLimiter(new MemoryStore(), {
capacity: 10,
refillRatePerSec: 1,
});
const result = await limiter.check('user-123');
// { allowed: true, remaining: 9, resetAt: 1735689660000 }import { Redis } from 'ioredis';
import { RedisStore, FixedWindowRateLimiter } from '@iuashrafi/rate-limiter';
const client = new Redis(process.env.REDIS_URL);
const store = new RedisStore({ client }); // failOpen: true to allow traffic through on a Redis outage
const limiter = new FixedWindowRateLimiter(store, {
windowSizeInSec: 60,
maxRequests: 100,
});
const result = await limiter.check('user-123');Every algorithm works against either storage backend without any code changes — swap
MemoryStore for RedisStore to go from single-process to distributed.
import express from 'express';
import { MemoryStore, FixedWindowRateLimiter, rateLimit } from '@iuashrafi/rate-limiter';
const limiter = new FixedWindowRateLimiter(new MemoryStore(), {
windowSizeInSec: 60,
maxRequests: 100,
});
const app = express();
app.use(
rateLimit({
limiter,
limit: 100, // must match the limiter's own configured max — see docs/phase-6/ARCHITECTURE.md ADR-023
keyGenerator: (req) => req.ip, // default; override with an API key, user ID, etc.
headers: 'both', // 'legacy' | 'draft' | 'both' | false
}),
);A blocked request gets a 429 with Retry-After, X-RateLimit-*, and RateLimit-* headers set.
An allowed request gets the same rate-limit headers attached and falls through to your route.
| Algorithm | Storage interface | Best for |
|---|---|---|
FixedWindowRateLimiter |
IStorage |
simplest, cheapest — allows boundary bursts |
SlidingWindowLogRateLimiter |
ILogStorage |
exact, no boundary burst — highest memory cost |
SlidingWindowCounterRateLimiter |
IStorage & IWeightedCounterStorage |
good approximation, fixed-window-like cost |
TokenBucketRateLimiter |
ITokenBucketStorage |
smooths bursts, allows some burst by design |
LeakyBucketRateLimiter |
ILeakyBucketStorage |
strict, constant output rate |
See each algorithm's docs/phase-N/LEARN.md for the full trade-off discussion and worked
examples.
npm run benchmarkRuns an in-process MemoryStore vs. RedisStore micro-benchmark (the Redis section is skipped
with a message if no Redis is reachable at REDIS_URL, default redis://127.0.0.1:6379) and an
end-to-end autocannon HTTP throughput test against a minimal Express app with rateLimit()
installed.
npm test # run tests
npm run test:coverage # run tests with coverage
npm run lint # lint
npm run build # compile to dist/MIT — see LICENSE.