redis-lua-rate-limiter 1.0.0 โ 1.0.1
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 +225 -37
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1,24 +1,100 @@
|
|
|
1
|
-
# redis-lua-rate-limiter
|
|
2
1
|
|
|
3
|
-
|
|
2
|
+
### Redis-lua-rate-limiter
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
**Distributed, atomic, token-bucket rate limiter for Node.js powered by Redis Lua scripts.**
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
This one keeps the logic inside Redis using Lua for atomic safety.
|
|
6
|
+
> Works correctly across multiple Node.js instances with zero race conditions by keeping the rate-limiting logic inside Redis.
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## The Problem With Typical Express Rate Limiters
|
|
11
|
+
|
|
12
|
+
Most middleware like `express-rate-limit`:
|
|
13
|
+
|
|
14
|
+
* Store counters in memory
|
|
15
|
+
* Break when you scale to multiple servers
|
|
16
|
+
* Leak requests under concurrency
|
|
17
|
+
* Suffer from race conditions
|
|
18
|
+
|
|
19
|
+
This library solves that by:
|
|
20
|
+
|
|
21
|
+
> Running the entire rate limiting algorithm **inside Redis** using **Lua**, executed atomically.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## What is the Token Bucket Algorithm?
|
|
26
|
+
|
|
27
|
+
Token Bucket is an industry standard algorithm used by:
|
|
28
|
+
|
|
29
|
+
* API Gateways
|
|
30
|
+
* CDNs
|
|
31
|
+
* Reverse Proxies
|
|
32
|
+
* Cloud providers
|
|
33
|
+
|
|
34
|
+
### How it works
|
|
35
|
+
|
|
36
|
+
Each user / API key / IP has a **bucket**:
|
|
37
|
+
|
|
38
|
+
* Bucket has **capacity** (max tokens)
|
|
39
|
+
* Tokens **refill over time**
|
|
40
|
+
* Each request **consumes 1 token**
|
|
41
|
+
* If bucket is empty โ request denied
|
|
42
|
+
|
|
43
|
+
This allows:
|
|
44
|
+
|
|
45
|
+
* Short bursts of traffic
|
|
46
|
+
* Strict control of long-term rate
|
|
47
|
+
* Smooth throttling instead of hard blocks
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
|
|
51
|
+
| Setting | Value |
|
|
52
|
+
| ----------- | ----------------- |
|
|
53
|
+
| Capacity | 10 |
|
|
54
|
+
| Refill Rate | 5 tokens / second |
|
|
55
|
+
|
|
56
|
+
User can send 10 requests instantly, then 5 per second after.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Why Redis + Lua?
|
|
61
|
+
|
|
62
|
+
Without Lua:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
GET tokens
|
|
66
|
+
CHECK tokens
|
|
67
|
+
SET tokens
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This causes race conditions under concurrency.
|
|
71
|
+
|
|
72
|
+
With Lua:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
All logic runs atomically inside Redis.
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
No race. No burst leak. Fully distributed.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Install
|
|
11
83
|
|
|
12
84
|
```bash
|
|
13
85
|
npm i redis-lua-rate-limiter
|
|
14
86
|
```
|
|
15
87
|
|
|
16
|
-
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Basic Usage (IP based)
|
|
17
91
|
|
|
18
92
|
```ts
|
|
19
93
|
import express from "express";
|
|
20
94
|
import { createRateLimiter } from "redis-lua-rate-limiter";
|
|
21
95
|
|
|
96
|
+
const app = express();
|
|
97
|
+
|
|
22
98
|
const limiter = await createRateLimiter({
|
|
23
99
|
redisUrl: "redis://localhost:6379",
|
|
24
100
|
default: { capacity: 10, refillRate: 5 },
|
|
@@ -26,53 +102,165 @@ const limiter = await createRateLimiter({
|
|
|
26
102
|
});
|
|
27
103
|
|
|
28
104
|
app.use(limiter.middleware());
|
|
105
|
+
|
|
106
|
+
app.get("/", (_, res) => res.send("OK"));
|
|
107
|
+
|
|
108
|
+
app.listen(3000);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## ๐ API Key Based Rate Limiting
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const limiter = await createRateLimiter({
|
|
117
|
+
redisUrl: "redis://localhost:6379",
|
|
118
|
+
default: { capacity: 20, refillRate: 10 },
|
|
119
|
+
|
|
120
|
+
keyGenerator: (req) => {
|
|
121
|
+
const apiKey = req.headers["x-api-key"];
|
|
122
|
+
if (typeof apiKey === "string") return `rate:${apiKey}`;
|
|
123
|
+
return `rate:${req.ip}`;
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## ๐ฃ๏ธ Per Route Limits
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const limiter = await createRateLimiter({
|
|
134
|
+
redisUrl: "redis://localhost:6379",
|
|
135
|
+
|
|
136
|
+
default: { capacity: 10, refillRate: 5 },
|
|
137
|
+
|
|
138
|
+
routes: {
|
|
139
|
+
"/login": { capacity: 3, refillRate: 1 },
|
|
140
|
+
"/heavy": { capacity: 2, refillRate: 0.5 },
|
|
141
|
+
},
|
|
142
|
+
});
|
|
29
143
|
```
|
|
30
144
|
|
|
31
|
-
|
|
145
|
+
---
|
|
32
146
|
|
|
33
|
-
|
|
34
|
-
- Redis Lua atomic execution
|
|
35
|
-
- Per API key / IP limiting
|
|
36
|
-
- Per route limits
|
|
37
|
-
- Rate limit headers
|
|
38
|
-
- Works across multiple Node servers
|
|
147
|
+
## ๐ก Rate Limit Headers
|
|
39
148
|
|
|
40
|
-
|
|
149
|
+
When `headers: true`:
|
|
41
150
|
|
|
42
151
|
```
|
|
43
|
-
|
|
44
|
-
|
|
152
|
+
X-RateLimit-Limit: 10
|
|
153
|
+
X-RateLimit-Remaining: 4
|
|
45
154
|
```
|
|
46
155
|
|
|
47
|
-
|
|
156
|
+
Similar to real API gateways.
|
|
48
157
|
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Options Reference
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
createRateLimiter({
|
|
164
|
+
redisUrl: string, // required
|
|
165
|
+
|
|
166
|
+
default: {
|
|
167
|
+
capacity: number,
|
|
168
|
+
refillRate: number
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
routes?: Record<string, {
|
|
172
|
+
capacity: number,
|
|
173
|
+
refillRate: number
|
|
174
|
+
}>,
|
|
175
|
+
|
|
176
|
+
keyGenerator?: (req: Request) => string,
|
|
177
|
+
|
|
178
|
+
headers?: boolean
|
|
179
|
+
})
|
|
49
180
|
```
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
โ Req/Sec โ 2,739 โ 2,739 โ 4,595 โ 5,003 โ 4,516.4 โ 558.22 โ 2,739 โ
|
|
60
|
-
โโโโโโโโโโโโโผโโโโโโโโโผโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโผโโโโโโโโโค
|
|
61
|
-
โ Bytes/Sec โ 898 kB โ 898 kB โ 1.51 MB โ 1.64 MB โ 1.48 MB โ 183 kB โ 897 kB โ
|
|
62
|
-
โโโโโโโโโโโโโดโโโโโโโโโดโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโดโโโโโโโโโ
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## ๐งช Benchmark (autocannon)
|
|
185
|
+
|
|
186
|
+
Tested under heavy concurrency:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
npx autocannon -c 1000 -d 15 http://localhost:3000
|
|
63
190
|
```
|
|
64
|
-
|
|
65
|
-
|
|
191
|
+
|
|
192
|
+
Result:
|
|
193
|
+
|
|
66
194
|
```
|
|
67
195
|
160 2xx responses, 67579 non 2xx responses
|
|
68
|
-
69k requests in
|
|
69
|
-
|
|
196
|
+
69k requests in 15s
|
|
197
|
+
~4500 req/sec
|
|
70
198
|
```
|
|
71
199
|
|
|
72
|
-
|
|
200
|
+
This shows:
|
|
201
|
+
|
|
202
|
+
* Strict enforcement
|
|
203
|
+
* No race condition
|
|
204
|
+
* No burst leak
|
|
205
|
+
* Fully atomic behavior
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Docker (for Redis)
|
|
210
|
+
|
|
211
|
+
```yaml
|
|
212
|
+
version: "3.9"
|
|
213
|
+
services:
|
|
214
|
+
redis:
|
|
215
|
+
image: redis:7
|
|
216
|
+
ports:
|
|
217
|
+
- "6379:6379"
|
|
218
|
+
```
|
|
73
219
|
|
|
74
|
-
|
|
220
|
+
Run:
|
|
75
221
|
|
|
76
222
|
```bash
|
|
77
223
|
docker compose up
|
|
78
224
|
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Architecture
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
Client โ Express โ Redis Lua โ Allow / Deny
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
All Node instances share the same Redis logic.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Features
|
|
239
|
+
|
|
240
|
+
* Token bucket algorithm
|
|
241
|
+
* Redis Lua atomic execution
|
|
242
|
+
* Works across multiple servers
|
|
243
|
+
* Per IP / API key / user limiting
|
|
244
|
+
* Per route limits
|
|
245
|
+
* Rate limit headers
|
|
246
|
+
* TypeScript support
|
|
247
|
+
* Production ready
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## When should you use this?
|
|
252
|
+
|
|
253
|
+
* Horizontally scaled Node.js apps
|
|
254
|
+
* Microservices
|
|
255
|
+
* API servers
|
|
256
|
+
* Authentication endpoints
|
|
257
|
+
* Public APIs
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## ๐ License
|
|
262
|
+
|
|
263
|
+
MIT
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "redis-lua-rate-limiter",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Distributed token-bucket rate limiter using Redis Lua scripts for Node.js/Express",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
-
"files": [
|
|
8
|
-
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"lua"
|
|
10
|
+
],
|
|
9
11
|
"scripts": {
|
|
10
12
|
"build": "tsc",
|
|
11
13
|
"dev": "ts-node-dev example/express-app.ts"
|
|
@@ -21,7 +23,6 @@
|
|
|
21
23
|
"author": "shubhankar kumar singh",
|
|
22
24
|
"license": "MIT",
|
|
23
25
|
"dependencies": {
|
|
24
|
-
|
|
25
26
|
"ioredis": "^5.9.2"
|
|
26
27
|
},
|
|
27
28
|
"peerDependencies": {
|