@indiekitai/throttled
v0.1.0
Published
Rate limiting for TypeScript/Node.js - 5 algorithms, memory & Redis stores
Maintainers
Readme
@indiekit/throttled
Lightweight, zero-dependency rate limiting for TypeScript/Node.js.
- 5 algorithms — Token Bucket, Leaky Bucket, Fixed Window, Sliding Window, GCRA
- 2 stores — Memory (built-in LRU) and Redis (via ioredis)
- Express middleware — drop-in
429handling with standard headers - MCP server — expose rate limiting as AI-agent tools
- CLI — test rate limits from the terminal with JSON output
- TypeScript-first — full type exports, JSDoc on all public APIs
Install
npm install @indiekit/throttledFor Redis support:
npm install @indiekit/throttled ioredisQuick Start
import { Throttled } from '@indiekit/throttled';
const limiter = new Throttled({
algorithm: 'token-bucket',
limit: 10,
period: '1s',
});
const result = limiter.allow('user:123');
if (result.limited) {
console.log(`Rate limited. Retry after ${result.state.retryAfter}s`);
} else {
console.log(`Allowed. ${result.state.remaining} remaining`);
}Algorithms
Token Bucket
Tokens refill at a steady rate. Good for APIs allowing short bursts.
const limiter = new Throttled({
algorithm: 'token-bucket',
limit: 100, // 100 tokens max
period: '1m', // refill 100 tokens per minute
burst: 150, // allow bursts up to 150
});Use when: You want to allow short bursts while enforcing an average rate. Most common choice for API rate limiting.
Leaky Bucket
Requests drain at a fixed rate. Smooths out traffic spikes.
const limiter = new Throttled({
algorithm: 'leaky-bucket',
limit: 10,
period: '1s',
});Use when: You need smooth, steady throughput — e.g. sending messages to an external API that can't handle bursts.
Fixed Window
Counts requests in fixed time windows. Simple and predictable.
const limiter = new Throttled({
algorithm: 'fixed-window',
limit: 1000,
period: '1h',
});Use when: Simple counting is enough and you don't mind a burst at window boundaries. Good for billing/quota enforcement.
Sliding Window
Combines current and previous window with weighted counting. Eliminates boundary burst issues.
const limiter = new Throttled({
algorithm: 'sliding-window',
limit: 100,
period: '1m',
});Use when: You want fixed-window simplicity with better accuracy at window edges.
GCRA (Generic Cell Rate Algorithm)
Single-counter algorithm equivalent to a virtual scheduling approach. Memory-efficient.
const limiter = new Throttled({
algorithm: 'gcra',
limit: 10,
period: '1s',
});Use when: You want the most memory-efficient algorithm (1 value per key). Great for distributed systems.
Express Middleware
import express from 'express';
import { throttleMiddleware } from '@indiekit/throttled/express';
const app = express();
app.use(throttleMiddleware({
algorithm: 'sliding-window',
limit: 100,
period: '1m',
keyFn: (req) => req.ip,
}));
app.get('/api/data', (req, res) => {
res.json({ ok: true });
});Sets standard headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After (on 429).
Redis Store
import Redis from 'ioredis';
import { Throttled, RedisStore } from '@indiekit/throttled';
const redis = new Redis();
const limiter = new Throttled({
algorithm: 'token-bucket',
limit: 100,
period: '1m',
store: new RedisStore(redis),
});CLI
Test rate limits from your terminal:
npx @indiekit/throttled test --algorithm token-bucket --limit 10 --period 1sOptions:
| Flag | Default | Description |
|------|---------|-------------|
| --algorithm | token-bucket | Algorithm to use |
| --limit | 10 | Max requests per period |
| --period | 1s | Time period |
| --key | test | Rate limit key |
| --requests | 1 | Number of requests to simulate |
| --cost | 1 | Cost per request |
Output is JSON. Exit code 0 = allowed, 1 = rate limited.
{
"limited": false,
"state": {
"limit": 10,
"remaining": 9,
"resetAfter": 1,
"retryAfter": 0
}
}MCP Server
Expose rate limiting as AI-agent tools:
npx @indiekit/throttled mcpTools:
- check_rate — Check if a key is rate limited
- get_status — Get current state of a key
- reset — Reset a key's rate limit state
API Reference
new Throttled(options?)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| algorithm | AlgorithmType | 'token-bucket' | Algorithm |
| limit | number | 60 | Requests per period |
| period | PeriodString | '1m' | Period ('10s', '1m', '1h', '1d') |
| burst | number | limit | Burst capacity |
| store | Store | MemoryStore | Storage backend |
| key | string | — | Default key |
| cost | number | 1 | Default cost per request |
throttled.allow(key?, cost?): RateLimitResult
Check if a request is allowed, consuming cost from the quota.
throttled.peek(key?): RateLimitState
Peek at current state without consuming quota.
RateLimitResult
interface RateLimitResult {
limited: boolean;
state: RateLimitState;
}RateLimitState
interface RateLimitState {
limit: number; // Max requests
remaining: number; // Remaining in current window
resetAfter: number; // Seconds until full reset
retryAfter: number; // Seconds until retry (0 if allowed)
}Quota Helpers
import { perSec, perMin, perHour, perDay, createQuota, parsePeriod } from '@indiekit/throttled';
const q1 = perSec(10); // 10 req/sec
const q2 = perMin(100, 150); // 100 req/min, burst 150
const q3 = perHour(1000); // 1000 req/hour
const q4 = createQuota({ period: parsePeriod('30s'), limit: 50 });Express Middleware
import { throttleMiddleware } from '@indiekit/throttled/express';
throttleMiddleware({
...throttledOptions,
keyFn: (req) => req.ip, // Custom key extraction
});Comparison
| Feature | @indiekit/throttled | bottleneck | p-limit | rate-limiter-flexible | |---------|:------------------:|:----------:|:-------:|:---------------------:| | Algorithms | 5 | 1 | 0 | 5 | | TypeScript | ✅ native | ❌ @types | ✅ | ❌ @types | | Memory store | ✅ LRU | ✅ | N/A | ✅ | | Redis store | ✅ | ✅ | N/A | ✅ | | Express middleware | ✅ | ❌ | N/A | ✅ | | Zero deps | ✅ | ✅ | ✅ | ❌ | | MCP server | ✅ | ❌ | ❌ | ❌ | | CLI | ✅ | ❌ | ❌ | ❌ | | ESM + CJS | ✅ | ✅ | ✅ | ✅ | | Bundle size | ~5 KB | ~30 KB | ~1 KB | ~50 KB |
bottleneck — Job scheduler/queue with concurrency control. Different use case (job scheduling vs rate limiting).
p-limit — Concurrency limiter, not a rate limiter. Limits parallel execution, not requests per time window.
rate-limiter-flexible — Feature-rich but heavier. Good if you need Mongo/Memcached/Cluster stores. @indiekit/throttled is lighter with better TypeScript and agent tooling.
License
MIT
