nestjs-cluster-throttle 1.0.2 → 1.0.3

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 (2) hide show
  1. package/README.md +255 -32
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,17 +1,20 @@
1
1
  # nestjs-cluster-throttle
2
2
 
3
+ [![npm version](https://badge.fury.io/js/nestjs-cluster-throttle.svg)](https://badge.fury.io/js/nestjs-cluster-throttle) [![npm downloads](https://img.shields.io/npm/dm/nestjs-cluster-throttle.svg)](https://www.npmjs.com/package/nestjs-cluster-throttle) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Coverage Status](https://img.shields.io/badge/coverage-80%25-brightgreen)](https://claude.ai/chat/coverage) [![CI](https://github.com/your-username/nestjs-cluster-throttle/workflows/CI/badge.svg)](https://github.com/your-username/nestjs-cluster-throttle/actions)
4
+
3
5
  Cluster-ready rate limiting module for NestJS with Redis support and multiple rate limiting strategies.
4
6
 
5
7
  ## Features
6
8
 
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
9
+ - **Cluster-ready** - Works seamlessly in clustered environments
10
+ - **Redis support** - Distributed rate limiting with Redis
11
+ - **Memory storage** - In-memory storage for single-instance applications
12
+ - **Flexible strategies** - Support for fixed-window, token-bucket, and sliding-window algorithms
13
+ - **Decorator-based** - Easy-to-use decorators for route protection
14
+ - **Geo-blocking** - IP-based country restrictions with multiple providers (internal, IP-API, custom)
15
+ - **Highly configurable** - Customize limits, windows, and behavior
16
+ - **Type-safe** - Written in TypeScript with full type support
17
+ - **Fail-open strategy** - Gracefully handles storage failures
15
18
 
16
19
  ## Installation
17
20
 
@@ -195,6 +198,131 @@ RateLimitModule.forRoot({
195
198
  })
196
199
  ```
197
200
 
201
+ ## Geo-blocking (IP-based Country Restrictions)
202
+
203
+ ### Basic Geo-blocking
204
+
205
+ Block or allow specific countries:
206
+
207
+ ```typescript
208
+ import { Controller, Get } from '@nestjs/common';
209
+ import { RateLimit } from 'nestjs-cluster-throttle';
210
+
211
+ @Controller('api')
212
+ export class ApiController {
213
+ @Get('us-only')
214
+ @RateLimit({
215
+ windowMs: 60000,
216
+ max: 100,
217
+ geoLocation: {
218
+ allowedCountries: ['US', 'CA'], // Only US and Canada
219
+ message: 'This service is only available in North America',
220
+ statusCode: 403,
221
+ },
222
+ })
223
+ usOnlyEndpoint() {
224
+ return { message: 'Welcome, North American user!' };
225
+ }
226
+
227
+ @Get('block-countries')
228
+ @RateLimit({
229
+ windowMs: 60000,
230
+ max: 100,
231
+ geoLocation: {
232
+ blockedCountries: ['CN', 'RU'], // Block specific countries
233
+ message: 'Access denied from your country',
234
+ },
235
+ })
236
+ restrictedEndpoint() {
237
+ return { message: 'Access granted' };
238
+ }
239
+ }
240
+ ```
241
+
242
+ ### Geo Providers
243
+
244
+ Choose from multiple geo-location providers:
245
+
246
+ ```typescript
247
+ RateLimitModule.forRoot({
248
+ windowMs: 60000,
249
+ max: 100,
250
+ geoLocation: {
251
+ provider: 'ip-api', // Options: 'internal', 'ip-api', 'custom'
252
+ allowedCountries: ['US', 'GB', 'DE'],
253
+ },
254
+ })
255
+ ```
256
+
257
+ #### Available Providers
258
+
259
+ 1. **internal** (default) - Basic IP range checking, good for testing
260
+ 2. **ip-api** - Free service from ip-api.com (45 req/min limit)
261
+ 3. **custom** - Bring your own provider
262
+
263
+ ### Custom Geo Provider
264
+
265
+ ```typescript
266
+ import { GeoLocationProvider, GeoLocationResult } from 'nestjs-cluster-throttle';
267
+
268
+ class MyGeoProvider implements GeoLocationProvider {
269
+ async lookup(ip: string): Promise<GeoLocationResult | null> {
270
+ // e.g., MaxMind GeoIP2, IPStack, etc.
271
+ return {
272
+ country: 'United States',
273
+ countryCode: 'US',
274
+ city: 'New York',
275
+ lat: 40.7128,
276
+ lon: -74.0060,
277
+ };
278
+ }
279
+ }
280
+
281
+ RateLimitModule.forRoot({
282
+ windowMs: 60000,
283
+ max: 100,
284
+ geoLocation: {
285
+ provider: 'custom',
286
+ customProvider: new MyGeoProvider(),
287
+ allowedCountries: ['US'],
288
+ },
289
+ })
290
+ ```
291
+
292
+ ### Geo-block Callback
293
+
294
+ Get notified when a request is geo-blocked:
295
+
296
+ ```typescript
297
+ @RateLimit({
298
+ windowMs: 60000,
299
+ max: 100,
300
+ geoLocation: {
301
+ blockedCountries: ['XX'],
302
+ onGeoBlock: (ip, country, request) => {
303
+ console.log(`Blocked request from ${country} (${ip})`);
304
+ // Log to analytics, notify admin, etc.
305
+ },
306
+ },
307
+ })
308
+ ```
309
+
310
+ ### Global Geo-blocking
311
+
312
+ Apply geo-restrictions globally:
313
+
314
+ ```typescript
315
+ RateLimitModule.forRoot({
316
+ windowMs: 60000,
317
+ max: 100,
318
+ geoLocation: {
319
+ provider: 'ip-api',
320
+ blockedCountries: ['XX', 'YY'],
321
+ message: 'Service not available in your region',
322
+ },
323
+ })
324
+ ```
325
+
198
326
  ## Rate Limiting Strategies
199
327
 
200
328
  ### Fixed Window (Default)
@@ -266,32 +394,45 @@ export class MyService {
266
394
 
267
395
  ## Configuration Options
268
396
 
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 |
397
+ |Option|Type|Default|Description|
398
+ |---|---|---|---|
399
+ |`windowMs`|number|`900000` (15 min)|Time window in milliseconds|
400
+ |`max`|number|`100`|Maximum number of requests per window|
401
+ |`message`|string|`'Too Many Requests'`|Error message when limit is exceeded|
402
+ |`statusCode`|number|`429`|HTTP status code when limit is exceeded|
403
+ |`skipSuccessfulRequests`|boolean|`false`|Skip successful requests in counting|
404
+ |`keyGenerator`|function|IP-based|Function to generate rate limit key|
405
+ |`skip`|function|`undefined`|Function to conditionally skip rate limiting|
406
+ |`handler`|function|`undefined`|Custom handler for rate limit exceeded|
407
+ |`clusterMode`|boolean|`false`|Enable Redis for cluster mode|
408
+ |`redisOptions`|object|`{}`|Redis connection options|
409
+ |`strategy`|string|`'fixed-window'`|Rate limiting strategy|
410
+ |`burstCapacity`|number|`undefined`|Burst capacity for token-bucket|
411
+ |`fillRate`|number|`undefined`|Fill rate for token-bucket|
412
+ |`geoLocation`|object|`undefined`|Geo-blocking configuration|
413
+
414
+ ### Geo-Location Options
415
+
416
+ |Option|Type|Default|Description|
417
+ |---|---|---|---|
418
+ |`provider`|string|`'internal'`|Geo provider: 'internal', 'ip-api', 'custom'|
419
+ |`customProvider`|GeoLocationProvider|`undefined`|Custom geo provider implementation|
420
+ |`allowedCountries`|string[]|`undefined`|ISO country codes to allow|
421
+ |`blockedCountries`|string[]|`undefined`|ISO country codes to block|
422
+ |`onGeoBlock`|function|`undefined`|Callback when request is geo-blocked|
423
+ |`message`|string|Auto-generated|Error message for geo-blocked requests|
424
+ |`statusCode`|number|`403`|HTTP status for geo-blocked requests|
284
425
 
285
426
  ### Redis Options
286
427
 
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 |
428
+ |Option|Type|Default|Description|
429
+ |---|---|---|---|
430
+ |`host`|string|`'localhost'`|Redis host|
431
+ |`port`|number|`6379`|Redis port|
432
+ |`password`|string|`undefined`|Redis password|
433
+ |`db`|number|`0`|Redis database number|
434
+ |`keyPrefix`|string|`'rate-limit:'`|Key prefix for Redis|
435
+ |`enableReadyCheck`|boolean|`true`|Enable ready check|
295
436
 
296
437
  ## Response Headers
297
438
 
@@ -305,4 +446,86 @@ When rate limiting is active, the following headers are set:
305
446
 
306
447
  ```typescript
307
448
  import { Test } from '@nestjs/testing';
308
- import { RateLimitModule, RateLimitService }
449
+ import { RateLimitModule, RateLimitService } from 'nestjs-cluster-throttle';
450
+
451
+ describe('RateLimitService', () => {
452
+ let service: RateLimitService;
453
+
454
+ beforeEach(async () => {
455
+ const module = await Test.createTestingModule({
456
+ imports: [
457
+ RateLimitModule.forRoot({
458
+ windowMs: 1000,
459
+ max: 5,
460
+ }),
461
+ ],
462
+ }).compile();
463
+
464
+ service = module.get<RateLimitService>(RateLimitService);
465
+ });
466
+
467
+ it('should limit requests', async () => {
468
+ const request = { ip: '127.0.0.1', method: 'GET', url: '/test' };
469
+
470
+ // First 5 requests should be allowed
471
+ for (let i = 0; i < 5; i++) {
472
+ const result = await service.checkRateLimit(request);
473
+ expect(result.allowed).toBe(true);
474
+ }
475
+
476
+ // 6th request should be blocked
477
+ const result = await service.checkRateLimit(request);
478
+ expect(result.allowed).toBe(false);
479
+ });
480
+ });
481
+ ```
482
+
483
+ ## Best Practices
484
+
485
+ 1. **Use Redis in production**: For multi-instance deployments, always use Redis to ensure consistent rate limiting across instances.
486
+
487
+ 2. **Choose appropriate windows**: Shorter windows (1-5 minutes) for strict limits, longer windows (15-60 minutes) for general API protection.
488
+
489
+ 3. **Set realistic limits**: Consider your API's capacity and user needs. Start conservative and adjust based on monitoring.
490
+
491
+ 4. **Use custom key generators**: For authenticated APIs, rate limit by user ID rather than IP to prevent shared IP issues.
492
+
493
+ 5. **Monitor rate limit hits**: Track how often users hit limits to adjust thresholds appropriately.
494
+
495
+ 6. **Implement skip logic**: Exempt health checks, webhooks, or trusted services from rate limiting.
496
+
497
+
498
+ ## Performance Considerations
499
+
500
+ - **Memory Store**: Fast but not suitable for cluster mode. Memory usage grows with unique IPs.
501
+ - **Redis Store**: Adds minimal latency (~1-2ms) but enables cluster mode and reduces memory usage.
502
+ - **Lua Scripts**: Redis operations use Lua scripts for atomic operations, ensuring accuracy.
503
+
504
+ ## Troubleshooting
505
+
506
+ ### Rate limiting not working in cluster mode
507
+
508
+ Make sure Redis is properly configured and accessible from all instances:
509
+
510
+ ```typescript
511
+ RateLimitModule.forRoot({
512
+ clusterMode: true,
513
+ redisOptions: {
514
+ host: 'redis-host',
515
+ port: 6379,
516
+ enableReadyCheck: true,
517
+ },
518
+ })
519
+ ```
520
+
521
+ ### Rate limits reset unexpectedly
522
+
523
+ Check Redis persistence settings. If Redis restarts without persistence, all rate limit data is lost.
524
+
525
+ ## 🧑‍💻 Contributing
526
+ We love contributions! Found a bug or have an idea? Open an issue or submit a PR.
527
+
528
+ ---
529
+
530
+ ## 📜 License
531
+ This project is licensed under the MIT License. See the LICENSE file for details.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestjs-cluster-throttle",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Enterprise-grade rate limiting module for NestJS with Redis support, multiple strategies, and cluster mode",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",