{"_id":"@amarpreetbhatia/simple-rate-limiter","name":"@amarpreetbhatia/simple-rate-limiter","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@amarpreetbhatia/simple-rate-limiter","version":"1.0.0","description":"A Node.js Express rate limiter middleware with sliding window algorithm","homepage":"https://amarpreetbhatia.github.io/simple-rate-limiter","repository":{"type":"git","url":"git+https://github.com/amarpreetbhatia/simple-rate-limiter.git"},"bugs":{"url":"https://github.com/amarpreetbhatia/simple-rate-limiter/issues"},"prepare":"npm run build","engines":{"node":">=18"},"main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc","dev":"ts-node src/example.ts","start":"node dist/example.js","test":"jest","clean":"rm -rf dist","docs:clean":"rm -rf docs","docs:build":"npm run build && jsdoc -c jsdoc.json"},"keywords":["rate-limiting","middleware","express","sliding-window"],"author":"","license":"MIT","dependencies":{"express":"^4.18.2"},"devDependencies":{"@types/express":"^4.17.17","@types/jest":"^29.5.5","@types/node":"^20.3.1","@types/supertest":"^2.0.12","jest":"^29.5.0","jsdoc":"^4.0.3","supertest":"^7.2.2","ts-jest":"^29.1.0","typescript":"^5.1.3","ts-node":"^10.9.1"},"gitHead":"f61cbd1990adcb045ddadc87f670720760f2c1f6","_id":"@amarpreetbhatia/simple-rate-limiter@1.0.0","_nodeVersion":"24.14.0","_npmVersion":"11.9.0","dist":{"integrity":"sha512-RYsDNCzRDvkK6WurnGr1p+oQpUixd1Z1tHyf5tEOleyxd+RDR0kTsiKaehqKPP6s+W/RLLiPBKRDQEbIMkFbvA==","shasum":"6c636e77db1998fdb55182b9d479af6f1b5c5f04","tarball":"https://registry.npmjs.org/@amarpreetbhatia/simple-rate-limiter/-/simple-rate-limiter-1.0.0.tgz","fileCount":23,"unpackedSize":56043,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFa6jdN8+Uz+JyYFVzIHbWZQD39tMy/d9lZYs68VQXvJAiABWG8iYLpTAahCg18fM66X1y8GiaOdkT8HcXRO7ESVyw=="}]},"_npmUser":{"name":"amarpreetbhatia","email":"amarpreetbhatia@gmail.com"},"directories":{},"maintainers":[{"name":"amarpreetbhatia","email":"amarpreetbhatia@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/simple-rate-limiter_1.0.0_1780843735217_0.09573031169525903"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-07T14:48:54.993Z","1.0.0":"2026-06-07T14:48:55.341Z","modified":"2026-06-07T14:48:55.574Z"},"maintainers":[{"name":"amarpreetbhatia","email":"amarpreetbhatia@gmail.com"}],"description":"A Node.js Express rate limiter middleware with sliding window algorithm","homepage":"https://amarpreetbhatia.github.io/simple-rate-limiter","keywords":["rate-limiting","middleware","express","sliding-window"],"repository":{"type":"git","url":"git+https://github.com/amarpreetbhatia/simple-rate-limiter.git"},"bugs":{"url":"https://github.com/amarpreetbhatia/simple-rate-limiter/issues"},"license":"MIT","readme":"# simple-rate-limiter\n\nA TypeScript-based Express rate limiter middleware supporting both sliding window and token bucket algorithms for per-IP request limiting. Designed for transparency, observability, and extensibility.\n\n> Documentation is published with GitHub Pages at: https://amarpreetbhatia.github.io/simple-rate-limiter\n>\n> Generate locally with `npm run docs:build` and open the output in `docs/`.\n\n## Features\n\n- ✅ **Algorithm selection** – Supports both sliding window and token bucket modes\n- ✅ **Per-IP Rate Limiting** – Identifies clients by IP address (customizable)\n- ✅ **Configurable Storage** – In-memory store provided; bring your own Redis/Memcached\n- ✅ **Observability Hooks** – Metrics and monitoring integration support\n- ✅ **Standard Headers** – Sends `X-RateLimit-*` and `Retry-After` headers\n- ✅ **Safe Defaults** – Fails open; allows traffic if store fails\n- ✅ **TypeScript Support** – Full type safety and IDE autocomplete\n\n## Installation\n\n```bash\nnpm install express\nnpm install --save-dev @types/express typescript ts-node\n```\n\nOnce the package is published, install it with:\n\n```bash\nnpm install simple-rate-limiter\n```\n\n## Quick Start\n\n```typescript\nimport express from 'express';\nimport { createRateLimiter } from './index';\n\nconst app = express();\n\nconst limiter = createRateLimiter({\n  windowMs: 60_000,      // 60 seconds\n  maxRequests: 100,      // 100 requests per window\n  headers: true,         // Enable rate limit headers\n});\n\napp.use(limiter);\n\napp.get('/', (req, res) => {\n  res.send('OK');\n});\n\napp.listen(3000);\n```\n\n## Configuration\n\n### `RateLimiterConfig`\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `algorithm` | `'sliding-window' \\| 'token-bucket'` | `sliding-window` | Select which rate limiting algorithm to use |\n| `windowMs` | `number` | required | Sliding window interval or token bucket evaluation window in ms |\n| `maxRequests` | `number` | required | Allowed requests per window or token bucket capacity |\n| `tokenBucket` | `TokenBucketConfig` | none | Optional token bucket settings when using `token-bucket` |\n| `keyGenerator` | `(req) => string` | `req.ip` | Function to derive client key |\n| `skip` | `(req) => boolean` | none | Skip rate limiting for specific requests |\n| `headers` | `boolean \\| object` | `false` | Enable standard rate limit headers |\n| `store` | `RateLimiterStore` | `InMemoryStore` | Custom storage backend |\n| `metrics` | `RateLimiterMetrics` | none | Observability hooks |\n| `logger` | `RateLimiterLogger` | `console` | Custom logger instance |\n| `onLimitReached` | `(req, res, info) => void` | none | Callback when limit first reached |\n| `onBlocked` | `(req, res, info) => void` | none | Callback when request is blocked |\n\n## Usage Examples\n\n### Basic Setup\n\n```typescript\nconst limiter = createRateLimiter({\n  windowMs: 15 * 60 * 1000, // 15 minutes\n  maxRequests: 100,\n});\n\napp.use(limiter);\n```\n\n### With Custom Key Generator\n\nRate limit by user ID instead of IP:\n\n```typescript\nconst limiter = createRateLimiter({\n  windowMs: 60_000,\n  maxRequests: 50,\n  keyGenerator: (req) => req.user?.id || req.ip,\n});\n```\n\n### Skip Rate Limiting for Specific Routes\n\n```typescript\nconst limiter = createRateLimiter({\n  windowMs: 60_000,\n  maxRequests: 100,\n  skip: (req) => req.path === '/health' || req.path === '/status',\n});\n```\n\n### With Metrics Integration\n\n```typescript\nconst limiter = createRateLimiter({\n  windowMs: 60_000,\n  maxRequests: 100,\n  metrics: {\n    recordAllowed: (req, info) => {\n      // Send to Prometheus, Datadog, etc.\n      prometheus.counter('rate_limiter_allowed_total', 1);\n    },\n    recordBlocked: (req, info) => {\n      prometheus.counter('rate_limiter_blocked_total', 1);\n    },\n    recordCurrentUsage: (req, info) => {\n      prometheus.gauge('rate_limiter_current_requests', info.currentRequests);\n    },\n  },\n});\n```\n\n### With Custom Logger\n\n```typescript\nimport { RateLimiterLogger } from './index';\n\nclass CustomLogger implements RateLimiterLogger {\n  log(...args: any[]): void {\n    // Use your own logging system\n    myLogger.info(...args);\n  }\n\n  warn(...args: any[]): void {\n    myLogger.warn(...args);\n  }\n\n  error(...args: any[]): void {\n    myLogger.error(...args);\n  }\n}\n\nconst limiter = createRateLimiter({\n  windowMs: 60_000,\n  maxRequests: 100,\n  logger: new CustomLogger(), // Optional; defaults to console\n});\n```\n\n### Algorithm Selection\n\n```typescript\nconst limiter = createRateLimiter({\n  algorithm: 'token-bucket',\n  windowMs: 60_000,\n  maxRequests: 100,\n  tokenBucket: {\n    bucketSize: 100,\n    refillRate: 1, // one token per second\n  },\n});\n```\n\n### Custom Response on Block\n\n```typescript\nconst limiter = createRateLimiter({\n  windowMs: 60_000,\n  maxRequests: 100,\n  onBlocked: (req, res, info) => {\n    res.status(429).json({\n      error: 'Rate limit exceeded',\n      retryAfter: Math.ceil(info.resetInMs / 1000),\n    });\n  },\n});\n```\n\n### With Callbacks\n\n```typescript\nconst limiter = createRateLimiter({\n  windowMs: 60_000,\n  maxRequests: 100,\n  onLimitReached: (req, res, info) => {\n    console.warn(`Limit reached for ${info.key}`);\n    // Send alert, log, etc.\n  },\n  onBlocked: (req, res, info) => {\n    console.error(`Request blocked for ${info.key}`);\n    res.status(429).send('Too many requests');\n  },\n});\n```\n\n### With Custom Store (Redis Example)\n\n```typescript\nimport redis from 'redis';\n\nconst redisClient = redis.createClient();\n\nconst customStore: RateLimiterStore = {\n  async get(key: string) {\n    const data = await redisClient.get(key);\n    return data ? JSON.parse(data) : null;\n  },\n  async set(key: string, entry: any) {\n    await redisClient.set(key, JSON.stringify(entry), 'EX', 3600);\n  },\n  async reset(key: string) {\n    await redisClient.del(key);\n  },\n};\n\nconst limiter = createRateLimiter({\n  windowMs: 60_000,\n  maxRequests: 100,\n  store: customStore,\n});\n```\n\n## Response Headers\n\nWhen `headers: true`, the middleware adds:\n\n- `X-RateLimit-Limit` – Total requests allowed in the window\n- `X-RateLimit-Remaining` – Remaining requests in current window\n- `X-RateLimit-Reset` – Unix timestamp when window resets (seconds)\n- `Retry-After` – Seconds to wait before retrying (only when blocked)\n\n```\nHTTP/1.1 200 OK\nX-RateLimit-Limit: 100\nX-RateLimit-Remaining: 95\nX-RateLimit-Reset: 1622548234\n```\n\n## Error Responses\n\nWhen a request is blocked:\n\n```json\n{\n  \"error\": \"Too Many Requests\",\n  \"retryAfter\": 45\n}\n```\n\nStatus code: `429 Too Many Requests`\n\n## Running the Example\n\n```bash\n# Build the TypeScript\nnpm run build\n\n# Run the example server\nnpm run dev\n```\n\nThe server will start on `http://localhost:3000` with a 10 requests/60 seconds limit.\n\nTest with:\n\n```bash\n# Should succeed\ncurl http://localhost:3000/\n\n# Make 10 requests\nfor i in {1..10}; do curl http://localhost:3000/; done\n\n# 11th request should be blocked\ncurl http://localhost:3000/\n```\n\n## API Reference\n\n### `createRateLimiter(config: RateLimiterConfig): RequestHandler`\n\nFactory function that returns an Express middleware.\n\n### Types\n\n```typescript\ninterface TokenBucketConfig {\n  bucketSize?: number; // Maximum tokens in the bucket\n  refillRate?: number; // Tokens replenished per second\n}\n\ninterface RateLimitInfo {\n  key: string;                    // Client identifier\n  windowMs: number;               // Window size or evaluation interval\n  maxRequests: number;            // Request limit or bucket capacity\n  currentRequests: number;        // Used requests or consumed tokens\n  remainingRequests: number;      // Remaining allowed requests or available tokens\n  resetInMs: number;              // ms until the next reset or token refill\n}\n\ninterface RateLimiterLogger {\n  log(...args: any[]): void;      // General logging\n  warn(...args: any[]): void;     // Warning logging\n  error(...args: any[]): void;    // Error logging\n}\n\ninterface RateLimiterStore {\n  get(key: string): Promise<RateLimiterEntry | null>;\n  set(key: string, entry: RateLimiterEntry): Promise<void>;\n  reset(key: string): Promise<void>;\n}\n```\n\n## Algorithms\n\nThis middleware supports two rate limiting algorithms:\n\n- `sliding-window`: ideal for request quotas over a moving time window.\n- `token-bucket`: ideal for smoothing traffic and allowing controlled bursts.\n\n### Sliding window behavior\n\n- The middleware tracks request timestamps.\n- It counts requests in the interval `[now - windowMs, now]`.\n- If the count exceeds `maxRequests`, the request is blocked.\n\n### Token bucket behavior\n\n- Each client has a bucket of tokens.\n- The bucket refills at `refillRate` tokens per second.\n- Each request consumes one token.\n- If tokens are unavailable, the request is blocked until tokens replenish.\n\n## Error Handling\n\nThe middleware implements **fail-open** semantics:\n\n- If the store fails to respond, the request is **allowed**.\n- An error is logged for monitoring.\n- This ensures the rate limiter doesn't become a point of failure.\n\n## Performance Considerations\n\n- **In-memory store**: Suitable for single-instance deployments; avoid using it for very high client counts.\n- **Distributed store**: Use Redis/Memcached for multi-instance deployments.\n- **Cleanup**: In-memory store periodically removes stale entries to prevent memory growth.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-37b671345fd5e2838de8d0eb874c2236"}