redis-distributed-rate-limiter 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/luaScript.d.ts +2 -0
- package/dist/luaScript.d.ts.map +1 -0
- package/dist/luaScript.js +28 -0
- package/dist/luaScript.js.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# redis-distributed-rate-limiter
|
|
2
|
+
|
|
3
|
+
A high-performance, ultra-low-latency distributed rate-limiting middleware for Express.js applications. Built with atomic Redis Lua scripting to prevent race conditions across horizontally scaled infrastructure, featuring a decoupled asynchronous telemetry hook for real-time traffic observability.
|
|
4
|
+
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
|
|
7
|
+
## 🏗️ Core Architecture
|
|
8
|
+
|
|
9
|
+
This library isolates the critical path of API request evaluation from the secondary collection of analytical telemetry data. By executing a Rolling Sliding Window algorithm atomically within a single Redis thread, the middleware maintains a sub-3ms latency overhead while providing rich hooks to stream analytics to downstream systems like Apache Kafka, RabbitMQ, or managed logging databases.
|
|
10
|
+
|
|
11
|
+
* **Distributed Synchronization:** State is centralized in Redis, allowing multiple stateless API gateway instances to scale out horizontally while maintaining shared rate-limit quotas.
|
|
12
|
+
* **Atomic Sliding Window:** Uses custom Lua scripting (`ZREMRANGEBYSCORE` + `ZADD` + `ZCARD`) to guarantee thread safety and prevent window-boundary traffic exploitation.
|
|
13
|
+
* **Non-Blocking Telemetry Pipelines:** Exposes a clean event-driven interface that offloads log recording out of the main request-response lifecycle loop using `process.nextTick()`.
|
|
14
|
+
* **Resilient Fail-Open Design:** Automatically logs internal exceptions and fails open, preventing cache cluster outages from crashing customer-facing API nodes.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## ⚙️ Installation
|
|
19
|
+
|
|
20
|
+
Install the package via the npm registry:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install redis-distributed-rate-limiter
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Peer Dependencies
|
|
28
|
+
|
|
29
|
+
Ensure you have the official Redis client installed and running in your root application environment:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install redis
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 🚀 Quick Start (TypeScript / JavaScript)
|
|
39
|
+
|
|
40
|
+
Initialize the middleware by passing your pre-configured Redis client wrapper and setting up your threshold definitions:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import express from 'express';
|
|
44
|
+
import { createClient } from 'redis';
|
|
45
|
+
import { distributedRateLimiter, TelemetryLog } from 'redis-distributed-rate-limiter';
|
|
46
|
+
|
|
47
|
+
const app = express();
|
|
48
|
+
|
|
49
|
+
// 1. Initialize your centralized Redis client connection
|
|
50
|
+
const redisClient = createClient({ url: 'redis://localhost:6379' });
|
|
51
|
+
redisClient.connect().then(() => console.log('Redis connected successfully'));
|
|
52
|
+
|
|
53
|
+
// 2. Configure the Distributed Rate Limiter Middleware
|
|
54
|
+
const limiter = distributedRateLimiter({
|
|
55
|
+
redisClient: redisClient,
|
|
56
|
+
windowInMs: 60000, // 1 Minute moving sliding window
|
|
57
|
+
maxRequests: 10, // Allow up to 10 requests per window
|
|
58
|
+
|
|
59
|
+
// Custom non-blocking callback hook for async logging infrastructure
|
|
60
|
+
onLog: (logData: TelemetryLog) => {
|
|
61
|
+
// Example: Stream log payloads asynchronously to an Apache Kafka Producer,
|
|
62
|
+
// a microservice logger, or push to an analytics queue.
|
|
63
|
+
console.log(`[Telemetry Ingress] IP: ${logData.ip} | Status: ${logData.status} | Hits: ${logData.currentCount}`);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// 3. Apply the middleware cluster globally or to explicit routes
|
|
68
|
+
app.use(limiter);
|
|
69
|
+
|
|
70
|
+
app.get('/api/v1/resource', (req, res) => {
|
|
71
|
+
res.json({ success: true, message: "Welcome to the secure gateway." });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Note: If running behind reverse proxies (AWS ALB, Nginx, Cloudflare, Vercel)
|
|
75
|
+
app.set('trust proxy', true);
|
|
76
|
+
|
|
77
|
+
app.listen(3000, () => console.log('API Gateway active on port 3000'));
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 🎛️ Configuration Reference
|
|
84
|
+
|
|
85
|
+
The `distributedRateLimiter` initialization constructor accepts the following structured parameters:
|
|
86
|
+
|
|
87
|
+
| Property | Type | Required | Description |
|
|
88
|
+
| --- | --- | --- | --- |
|
|
89
|
+
| `redisClient` | `any` | **Yes** | An open, active v4 instance connection to your Redis deployment server. |
|
|
90
|
+
| `windowInMs` | `number` | **Yes** | Moving time framework window tracked in milliseconds (e.g., `60000` for 1 minute). |
|
|
91
|
+
| `maxRequests` | `number` | **Yes** | Total allowed operations inside the specific time frame constraint bounds. |
|
|
92
|
+
| `keyGenerator` | `(req) => string` | No | Overrides default key tracking logic. Allows tracking limits using authorization tokens, API keys, or User IDs instead of plain IP routing. |
|
|
93
|
+
| `onLog` | `(log: TelemetryLog) => void` | No | Callback hook firing asynchronously upon execution completion. Passes complete log payload packages. |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 📊 Telemetry Log Payload Schema
|
|
98
|
+
|
|
99
|
+
The `onLog` event emitter passes an immutable `TelemetryLog` object containing downstream performance values:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
interface TelemetryLog {
|
|
103
|
+
ip: string; // Remote user origin IP or mapped proxy client
|
|
104
|
+
path: string; // Visited API node endpoint URL string
|
|
105
|
+
method: string; // Executed HTTP request method verb (GET, POST, etc.)
|
|
106
|
+
status: 'ALLOWED' | 'BLOCKED'; // Resolution status string output
|
|
107
|
+
timestamp: number; // POSIX timestamp tracking exact request ingress time
|
|
108
|
+
currentCount: number; // Active tracking window hit state returned from Redis
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 🛡️ Response Headers
|
|
116
|
+
|
|
117
|
+
Successful operations return standard rate limit metadata embedded securely inside response headers:
|
|
118
|
+
|
|
119
|
+
```http
|
|
120
|
+
X-RateLimit-Limit: 10
|
|
121
|
+
X-RateLimit-Remaining: 9
|
|
122
|
+
X-RateLimit-Reset: 2026-07-05T11:04:48.201Z
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
When thresholds are broken, the middleware automatically rejects traffic with an explicit `429 Too Many Requests` status payload:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"status": 429,
|
|
131
|
+
"error": "Too Many Requests",
|
|
132
|
+
"message": "Rate limit exceeded. Please try again in 60 seconds."
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { LimiterConfig } from './types';
|
|
3
|
+
export declare function distributedRateLimiter(config: LimiterConfig): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,aAAa,IAO5C,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CA6D9E"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.distributedRateLimiter = distributedRateLimiter;
|
|
4
|
+
const luaScript_1 = require("./luaScript");
|
|
5
|
+
function distributedRateLimiter(config) {
|
|
6
|
+
const { redisClient, windowInMs, maxRequests, onLog } = config;
|
|
7
|
+
// Default key generator uses the client's IP address
|
|
8
|
+
const defaultKeyGen = (req) => `ratelimit:${req.ip || req.socket.remoteAddress}`;
|
|
9
|
+
const getKey = config.keyGenerator || defaultKeyGen;
|
|
10
|
+
return async (req, res, next) => {
|
|
11
|
+
// cast to any to avoid strict Request type incompatibilities across express versions
|
|
12
|
+
const key = getKey(req);
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
try {
|
|
15
|
+
// Execute the atomic Lua script in Redis
|
|
16
|
+
// eval(script, numKeys, keys, args)
|
|
17
|
+
const result = await redisClient.eval(luaScript_1.SLIDING_WINDOW_LUA, {
|
|
18
|
+
keys: [key],
|
|
19
|
+
arguments: [now.toString(), windowInMs.toString(), maxRequests.toString()]
|
|
20
|
+
});
|
|
21
|
+
const [isAllowed, currentCount] = result;
|
|
22
|
+
const remaining = Math.max(0, maxRequests - currentCount);
|
|
23
|
+
// Set standard rate-limiting headers
|
|
24
|
+
res.setHeader('X-RateLimit-Limit', maxRequests);
|
|
25
|
+
res.setHeader('X-RateLimit-Remaining', remaining);
|
|
26
|
+
res.setHeader('X-RateLimit-Reset', new Date(now + windowInMs).toISOString());
|
|
27
|
+
if (isAllowed === 1) {
|
|
28
|
+
// Trigger telemetry asynchronously without pausing the application cycle
|
|
29
|
+
if (onLog) {
|
|
30
|
+
process.nextTick(() => onLog({
|
|
31
|
+
ip: req.ip || 'unknown',
|
|
32
|
+
path: req.path,
|
|
33
|
+
method: req.method,
|
|
34
|
+
status: 'ALLOWED',
|
|
35
|
+
timestamp: now,
|
|
36
|
+
currentCount
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
return next();
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Block the request if the threshold is breached
|
|
43
|
+
if (onLog) {
|
|
44
|
+
process.nextTick(() => onLog({
|
|
45
|
+
ip: req.ip || 'unknown',
|
|
46
|
+
path: req.path,
|
|
47
|
+
method: req.method,
|
|
48
|
+
status: 'BLOCKED',
|
|
49
|
+
timestamp: now,
|
|
50
|
+
currentCount
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
res.status(429).json({
|
|
54
|
+
status: 429,
|
|
55
|
+
error: 'Too Many Requests',
|
|
56
|
+
message: `Rate limit exceeded. Please try again in ${Math.ceil(windowInMs / 1000)} seconds.`
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
// Fail-open strategy: If Redis fails, log the error and allow the traffic to pass
|
|
63
|
+
// so your security tool doesn't accidentally cause a total app outage.
|
|
64
|
+
console.error('Rate Limiter Internal Error:', error);
|
|
65
|
+
return next();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const SLIDING_WINDOW_LUA = "\n local key = KEYS[1]\n local now = tonumber(ARGV[1])\n local window = tonumber(ARGV[2])\n local limit = tonumber(ARGV[3])\n \n local clearBefore = now - window\n \n -- 1. Remove timestamps older than the current sliding window boundary\n redis.call('ZREMRANGEBYSCORE', key, '-inf', clearBefore)\n \n -- 2. Count how many requests are left in the current window\n local currentRequests = redis.call('ZCARD', key)\n \n -- 3. If below the limit, allow the request and log the current timestamp\n if currentRequests < limit then\n redis.call('ZADD', key, now, now)\n -- Set an expiry on the key so it cleans itself up if the user stops sending requests\n redis.call('EXPIRE', key, math.ceil(window / 1000))\n return {1, currentRequests + 1} -- [Allowed = true, New Count]\n else\n return {0, currentRequests} -- [Allowed = false, Current Count]\n end\n";
|
|
2
|
+
//# sourceMappingURL=luaScript.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"luaScript.d.ts","sourceRoot":"","sources":["../src/luaScript.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,23BAuB9B,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SLIDING_WINDOW_LUA = void 0;
|
|
4
|
+
exports.SLIDING_WINDOW_LUA = `
|
|
5
|
+
local key = KEYS[1]
|
|
6
|
+
local now = tonumber(ARGV[1])
|
|
7
|
+
local window = tonumber(ARGV[2])
|
|
8
|
+
local limit = tonumber(ARGV[3])
|
|
9
|
+
|
|
10
|
+
local clearBefore = now - window
|
|
11
|
+
|
|
12
|
+
-- 1. Remove timestamps older than the current sliding window boundary
|
|
13
|
+
redis.call('ZREMRANGEBYSCORE', key, '-inf', clearBefore)
|
|
14
|
+
|
|
15
|
+
-- 2. Count how many requests are left in the current window
|
|
16
|
+
local currentRequests = redis.call('ZCARD', key)
|
|
17
|
+
|
|
18
|
+
-- 3. If below the limit, allow the request and log the current timestamp
|
|
19
|
+
if currentRequests < limit then
|
|
20
|
+
redis.call('ZADD', key, now, now)
|
|
21
|
+
-- Set an expiry on the key so it cleans itself up if the user stops sending requests
|
|
22
|
+
redis.call('EXPIRE', key, math.ceil(window / 1000))
|
|
23
|
+
return {1, currentRequests + 1} -- [Allowed = true, New Count]
|
|
24
|
+
else
|
|
25
|
+
return {0, currentRequests} -- [Allowed = false, Current Count]
|
|
26
|
+
end
|
|
27
|
+
`;
|
|
28
|
+
//# sourceMappingURL=luaScript.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"luaScript.js","sourceRoot":"","sources":["../src/luaScript.ts"],"names":[],"mappings":";;;AAAa,QAAA,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;CAuBjC,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface TelemetryLog {
|
|
2
|
+
ip: string;
|
|
3
|
+
path: string;
|
|
4
|
+
method: string;
|
|
5
|
+
status: 'ALLOWED' | 'BLOCKED';
|
|
6
|
+
timestamp: number;
|
|
7
|
+
currentCount: number;
|
|
8
|
+
}
|
|
9
|
+
export interface LimiterConfig {
|
|
10
|
+
redisClient: any;
|
|
11
|
+
windowInMs: number;
|
|
12
|
+
maxRequests: number;
|
|
13
|
+
keyGenerator?: (req: Request) => string;
|
|
14
|
+
onLog?: (log: TelemetryLog) => void;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,GAAG,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IACxC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAC;CACrC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "redis-distributed-rate-limiter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "High-performance distributed rate-limiting middleware for Express using atomic Redis Lua scripting and non-blocking telemetry hooks.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"redis",
|
|
16
|
+
"rate-limiter",
|
|
17
|
+
"distributed",
|
|
18
|
+
"express",
|
|
19
|
+
"middleware",
|
|
20
|
+
"lua",
|
|
21
|
+
"sliding-window",
|
|
22
|
+
"telemetry",
|
|
23
|
+
"api-security"
|
|
24
|
+
],
|
|
25
|
+
"author": "Rohit Verma",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/express": "^5.0.0",
|
|
29
|
+
"@types/node": "^20.0.0",
|
|
30
|
+
"typescript": "^5.0.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"redis": "^4.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|