nestjs-cluster-throttle 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.
Files changed (3) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -0
  3. package/package.json +104 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 White Rabbit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,308 @@
1
+ # nestjs-cluster-throttle
2
+
3
+ Cluster-ready rate limiting module for NestJS with Redis support and multiple rate limiting strategies.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Cluster-ready** - Works seamlessly in clustered environments
8
+ - 🔴 **Redis support** - Distributed rate limiting with Redis
9
+ - 💾 **Memory storage** - In-memory storage for single-instance applications
10
+ - 🎯 **Flexible strategies** - Support for fixed-window, token-bucket, and sliding-window algorithms
11
+ - 🎨 **Decorator-based** - Easy-to-use decorators for route protection
12
+ - ⚙️ **Highly configurable** - Customize limits, windows, and behavior
13
+ - 🔒 **Type-safe** - Written in TypeScript with full type support
14
+ - 🛡️ **Fail-open strategy** - Gracefully handles storage failures
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install nestjs-cluster-throttle ioredis
20
+ # or
21
+ yarn add nestjs-cluster-throttle ioredis
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### Basic Usage (In-Memory)
27
+
28
+ ```typescript
29
+ import { Module } from '@nestjs/common';
30
+ import { RateLimitModule } from 'nestjs-cluster-throttle';
31
+
32
+ @Module({
33
+ imports: [
34
+ RateLimitModule.forRoot({
35
+ windowMs: 15 * 60 * 1000, // 15 minutes
36
+ max: 100, // limit each IP to 100 requests per windowMs
37
+ }),
38
+ ],
39
+ })
40
+ export class AppModule {}
41
+ ```
42
+
43
+ ### Redis-based (Cluster Mode)
44
+
45
+ ```typescript
46
+ import { Module } from '@nestjs/common';
47
+ import { RateLimitModule } from 'nestjs-cluster-throttle';
48
+
49
+ @Module({
50
+ imports: [
51
+ RateLimitModule.forRoot({
52
+ windowMs: 15 * 60 * 1000,
53
+ max: 100,
54
+ clusterMode: true,
55
+ redisOptions: {
56
+ host: 'localhost',
57
+ port: 6379,
58
+ password: 'your-password', // optional
59
+ db: 0,
60
+ keyPrefix: 'rate-limit:',
61
+ },
62
+ }),
63
+ ],
64
+ })
65
+ export class AppModule {}
66
+ ```
67
+
68
+ ### Async Configuration
69
+
70
+ ```typescript
71
+ import { Module } from '@nestjs/common';
72
+ import { RateLimitModule } from 'nestjs-cluster-throttle';
73
+ import { ConfigModule, ConfigService } from '@nestjs/config';
74
+
75
+ @Module({
76
+ imports: [
77
+ RateLimitModule.forRootAsync({
78
+ imports: [ConfigModule],
79
+ useFactory: async (configService: ConfigService) => ({
80
+ windowMs: configService.get('RATE_LIMIT_WINDOW_MS'),
81
+ max: configService.get('RATE_LIMIT_MAX'),
82
+ clusterMode: true,
83
+ redisOptions: {
84
+ host: configService.get('REDIS_HOST'),
85
+ port: configService.get('REDIS_PORT'),
86
+ password: configService.get('REDIS_PASSWORD'),
87
+ },
88
+ }),
89
+ inject: [ConfigService],
90
+ }),
91
+ ],
92
+ })
93
+ export class AppModule {}
94
+ ```
95
+
96
+ ## Usage in Controllers
97
+
98
+ ### Route-specific Rate Limiting
99
+
100
+ ```typescript
101
+ import { Controller, Get } from '@nestjs/common';
102
+ import { RateLimit } from 'nestjs-cluster-throttle';
103
+
104
+ @Controller('api')
105
+ export class ApiController {
106
+ @Get('limited')
107
+ @RateLimit({
108
+ windowMs: 60 * 1000, // 1 minute
109
+ max: 10, // 10 requests per minute
110
+ message: 'Too many requests from this IP',
111
+ statusCode: 429,
112
+ })
113
+ limitedEndpoint() {
114
+ return { message: 'This endpoint is rate limited' };
115
+ }
116
+
117
+ @Get('public')
118
+ publicEndpoint() {
119
+ return { message: 'This endpoint uses global rate limits' };
120
+ }
121
+ }
122
+ ```
123
+
124
+ ### Skip Rate Limiting
125
+
126
+ ```typescript
127
+ import { Controller, Get } from '@nestjs/common';
128
+ import { SkipRateLimit } from 'nestjs-cluster-throttle';
129
+
130
+ @Controller('api')
131
+ export class ApiController {
132
+ @Get('health')
133
+ @SkipRateLimit()
134
+ healthCheck() {
135
+ return { status: 'ok' };
136
+ }
137
+ }
138
+ ```
139
+
140
+ ## Advanced Configuration
141
+
142
+ ### Custom Key Generator
143
+
144
+ Use custom logic to generate rate limit keys:
145
+
146
+ ```typescript
147
+ RateLimitModule.forRoot({
148
+ windowMs: 15 * 60 * 1000,
149
+ max: 100,
150
+ keyGenerator: (request) => {
151
+ // Rate limit by user ID instead of IP
152
+ return request.user?.id || request.ip;
153
+ },
154
+ })
155
+ ```
156
+
157
+ ### Skip Requests Conditionally
158
+
159
+ ```typescript
160
+ RateLimitModule.forRoot({
161
+ windowMs: 15 * 60 * 1000,
162
+ max: 100,
163
+ skip: (request) => {
164
+ // Skip rate limiting for admin users
165
+ return request.user?.role === 'admin';
166
+ },
167
+ })
168
+ ```
169
+
170
+ ### Custom Handler
171
+
172
+ ```typescript
173
+ RateLimitModule.forRoot({
174
+ windowMs: 15 * 60 * 1000,
175
+ max: 100,
176
+ handler: (request, response) => {
177
+ // Custom handling when rate limit is exceeded
178
+ response.status(429).json({
179
+ error: 'Rate limit exceeded',
180
+ retryAfter: response.getHeader('X-RateLimit-Reset'),
181
+ });
182
+ },
183
+ })
184
+ ```
185
+
186
+ ### Skip Successful Requests
187
+
188
+ Only count failed requests towards the rate limit:
189
+
190
+ ```typescript
191
+ RateLimitModule.forRoot({
192
+ windowMs: 15 * 60 * 1000,
193
+ max: 100,
194
+ skipSuccessfulRequests: true,
195
+ })
196
+ ```
197
+
198
+ ## Rate Limiting Strategies
199
+
200
+ ### Fixed Window (Default)
201
+
202
+ ```typescript
203
+ RateLimitModule.forRoot({
204
+ windowMs: 60 * 1000,
205
+ max: 100,
206
+ strategy: 'fixed-window',
207
+ })
208
+ ```
209
+
210
+ ### Token Bucket
211
+
212
+ ```typescript
213
+ RateLimitModule.forRoot({
214
+ windowMs: 60 * 1000,
215
+ max: 100,
216
+ strategy: 'token-bucket',
217
+ burstCapacity: 150, // Allow bursts up to 150 requests
218
+ fillRate: 100, // Refill 100 tokens per windowMs
219
+ })
220
+ ```
221
+
222
+ ### Sliding Window
223
+
224
+ ```typescript
225
+ RateLimitModule.forRoot({
226
+ windowMs: 60 * 1000,
227
+ max: 100,
228
+ strategy: 'sliding-window',
229
+ })
230
+ ```
231
+
232
+ ## Programmatic Usage
233
+
234
+ Inject `RateLimitService` to check rate limits programmatically:
235
+
236
+ ```typescript
237
+ import { Injectable } from '@nestjs/common';
238
+ import { RateLimitService } from 'nestjs-cluster-throttle';
239
+
240
+ @Injectable()
241
+ export class MyService {
242
+ constructor(private rateLimitService: RateLimitService) {}
243
+
244
+ async checkLimit(request: any) {
245
+ const result = await this.rateLimitService.checkRateLimit(request, {
246
+ windowMs: 60 * 1000,
247
+ max: 10,
248
+ });
249
+
250
+ if (!result.allowed) {
251
+ throw new Error('Rate limit exceeded');
252
+ }
253
+
254
+ return result;
255
+ }
256
+
257
+ async resetUserLimit(userId: string) {
258
+ await this.rateLimitService.resetKey(userId);
259
+ }
260
+
261
+ async resetAllLimits() {
262
+ await this.rateLimitService.resetAll();
263
+ }
264
+ }
265
+ ```
266
+
267
+ ## Configuration Options
268
+
269
+ | Option | Type | Default | Description |
270
+ |--------|------|---------|-------------|
271
+ | `windowMs` | number | `900000` (15 min) | Time window in milliseconds |
272
+ | `max` | number | `100` | Maximum number of requests per window |
273
+ | `message` | string | `'Too Many Requests'` | Error message when limit is exceeded |
274
+ | `statusCode` | number | `429` | HTTP status code when limit is exceeded |
275
+ | `skipSuccessfulRequests` | boolean | `false` | Skip successful requests in counting |
276
+ | `keyGenerator` | function | IP-based | Function to generate rate limit key |
277
+ | `skip` | function | `undefined` | Function to conditionally skip rate limiting |
278
+ | `handler` | function | `undefined` | Custom handler for rate limit exceeded |
279
+ | `clusterMode` | boolean | `false` | Enable Redis for cluster mode |
280
+ | `redisOptions` | object | `{}` | Redis connection options |
281
+ | `strategy` | string | `'fixed-window'` | Rate limiting strategy |
282
+ | `burstCapacity` | number | `undefined` | Burst capacity for token-bucket |
283
+ | `fillRate` | number | `undefined` | Fill rate for token-bucket |
284
+
285
+ ### Redis Options
286
+
287
+ | Option | Type | Default | Description |
288
+ |--------|------|---------|-------------|
289
+ | `host` | string | `'localhost'` | Redis host |
290
+ | `port` | number | `6379` | Redis port |
291
+ | `password` | string | `undefined` | Redis password |
292
+ | `db` | number | `0` | Redis database number |
293
+ | `keyPrefix` | string | `'rate-limit:'` | Key prefix for Redis |
294
+ | `enableReadyCheck` | boolean | `true` | Enable ready check |
295
+
296
+ ## Response Headers
297
+
298
+ When rate limiting is active, the following headers are set:
299
+
300
+ - `X-RateLimit-Limit`: Maximum requests allowed
301
+ - `X-RateLimit-Remaining`: Requests remaining in current window
302
+ - `X-RateLimit-Reset`: Unix timestamp when the rate limit resets
303
+
304
+ ## Testing
305
+
306
+ ```typescript
307
+ import { Test } from '@nestjs/testing';
308
+ import { RateLimitModule, RateLimitService }
package/package.json ADDED
@@ -0,0 +1,104 @@
1
+ {
2
+ "name": "nestjs-cluster-throttle",
3
+ "version": "1.0.1",
4
+ "description": "Enterprise-grade rate limiting module for NestJS with Redis support, multiple strategies, and cluster mode",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc -p tsconfig.build.json",
14
+ "prebuild": "rimraf dist",
15
+ "test": "jest",
16
+ "test:watch": "jest --watch",
17
+ "test:cov": "jest --coverage",
18
+ "test:e2e": "jest --config ./test/jest-e2e.json",
19
+ "lint": "eslint \"{src,test}/**/*.ts\" --fix",
20
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
21
+ "prepublishOnly": "npm run build",
22
+ "prepare": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "nestjs",
26
+ "rate-limiting",
27
+ "rate-limiter",
28
+ "throttle",
29
+ "throttling",
30
+ "cluster",
31
+ "redis",
32
+ "distributed",
33
+ "rate-limit",
34
+ "guard",
35
+ "middleware",
36
+ "api-protection",
37
+ "ddos-protection",
38
+ "request-limiting",
39
+ "token-bucket",
40
+ "sliding-window",
41
+ "fixed-window"
42
+ ],
43
+ "author": {
44
+ "name": "Roman Dobrynin",
45
+ "email": "roman.dobrynin@gmail.com"
46
+ },
47
+ "contributors": [
48
+ {
49
+ "name": "Roman Dobrynin",
50
+ "email": "roman.dobrynin@gmail.com"
51
+ }
52
+ ],
53
+ "license": "MIT",
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/rdobrynin/nestjs-cluster-throttle.git"
57
+ },
58
+ "bugs": {
59
+ "url": "https://github.com/rdobrynin/nestjs-cluster-throttle/issues"
60
+ },
61
+ "homepage": "https://github.com/rdobrynin/nestjs-cluster-throttle#readme",
62
+ "engines": {
63
+ "node": ">=14.0.0",
64
+ "npm": ">=6.0.0"
65
+ },
66
+ "peerDependencies": {
67
+ "@nestjs/common": "^9.0.0 || ^10.0.0",
68
+ "@nestjs/core": "^9.0.0 || ^10.0.0",
69
+ "reflect-metadata": "^0.1.13",
70
+ "rxjs": "^7.0.0"
71
+ },
72
+ "dependencies": {
73
+ "ioredis": "^5.3.2"
74
+ },
75
+ "devDependencies": {
76
+ "@nestjs/common": "^10.0.0",
77
+ "@nestjs/core": "^10.0.0",
78
+ "@nestjs/platform-express": "^10.0.0",
79
+ "@nestjs/testing": "^10.0.0",
80
+ "@types/jest": "^29.5.0",
81
+ "@types/node": "^20.0.0",
82
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
83
+ "@typescript-eslint/parser": "^6.0.0",
84
+ "eslint": "^8.0.0",
85
+ "eslint-config-prettier": "^9.0.0",
86
+ "eslint-plugin-prettier": "^5.0.0",
87
+ "jest": "^29.5.0",
88
+ "prettier": "^3.0.0",
89
+ "reflect-metadata": "^0.1.13",
90
+ "rimraf": "^5.0.0",
91
+ "rxjs": "^7.8.0",
92
+ "ts-jest": "^29.1.0",
93
+ "ts-node": "^10.9.0",
94
+ "typescript": "^5.0.0"
95
+ },
96
+ "publishConfig": {
97
+ "access": "public",
98
+ "registry": "https://registry.npmjs.org/"
99
+ },
100
+ "np": {
101
+ "yarn": false,
102
+ "contents": "dist"
103
+ }
104
+ }