@nestjs/throttler

repository·master·Indexed 20 days ago

https://github.com/nestjs/throttler

A rate-limiting module for NestJS compatible with Express, Fastify, WebSockets, Socket.IO, and GraphQL. It allows developers to restrict the number of requests users can make to specific endpoints within defined time windows (TTL). Features include support for multiple named throttlers, custom tracker functions, global guards via ThrottlerGuard, and integration with external storage providers like Redis and MongoDB.

Tokens
10.5K
Snippets
35
Records
44
Agent score
71%

What's inside @nestjs/throttler

  1. Overview of @nestjs/throttler

    master

    The @nestjs/throttler package is a rate-limiter for NestJS that works across different contexts. It ensures users can only make a specific number of limit requests within a given ttl (Time To Live) period for each endpoint.

    Key features:

    • Identification: By default, users are identified by their IP address, but this can be customized using a getTracker function.
    • Storage: Includes a built-in in-memory cache for tracking requests, with support for external storage providers.
  2. Configure ThrottlerModule

    master

    The ThrottlerModule can be configured using forRoot or forRootAsync. You can define one or multiple throttling definitions by passing an array of configuration objects. Each object defines a ttl (time to live in milliseconds) and a limit (maximum requests within that TTL).

    @Module({
      imports: [
        ThrottlerModule.forRoot([{ 
          ttl: 60000, 
          limit: 10 
        }]),
      ],
    })
    export class AppModule {}
  3. Implement Throttling for GraphQL

    master

    To use ThrottlerGuard with GraphQL, extend the guard and override the getRequestResponse method to extract the request and response objects from the GraphQL context.

    ```typescript
    @Injectable
    export class GqlThrottlerGuard extends ThrottlerGuard {
      getRequestResponse(context: ExecutionContext) {
        const gqlCtx = GqlExecutionContext.create(context);
        const ctx = gqlCtx.getContext();
        return { req: ctx.req, res: ctx.res };
      }
    }

    Note on Context Configuration:

    • Apollo Server (Express): Set context in GraphQLModule using context: ({ req, res }) => ({ req, res }).
    • Apollo Server (Fastify) & Mercurius: Set context using context: (request, reply) => ({ request, reply }).
  4. Use community storage providers for Throttler

    master

    By default, @nestjs/throttler uses an in-memory storage provider. For distributed systems or persistent rate limiting, you can use community-maintained storage providers. Supported external storage options include:

    • Redis (based on node-redis)
    • Redis (based on ioredis)
    • MongoDB
  5. Bind ThrottlerGuard globally

    master

    After importing ThrottlerModule, you must bind the ThrottlerGuard to your application. To apply rate limiting globally to all routes, add ThrottlerGuard as a provider using the APP_GUARD token in any module.

    {
      provide: APP_GUARD,
      useClass: ThrottlerGuard
    }
  6. Handle proxies and IP extraction

    master

    If your application is behind a proxy, ensure your HTTP adapter (Express or Fastify) has trust proxy enabled to correctly populate the client IP.

    For Fastify, you may need to extend ThrottlerGuard and override getTracker to use req.ips (the array of IPs in the X-Forwarded-For header) instead of req.ip.

    // throttler-behind-proxy.guard.ts
    import { ThrottlerGuard } from '@nestjs/throttler';
    import { Injectable } from '@nestjs/common';
    
    @Injectable
    export class ThrottlerBehindProxyGuard extends ThrottlerGuard {
      protected getTracker(req: Record<string, any>): Promise<string> {
        const tracker = req.ips.length > 0 ? req.ips[0] : req.ip;
        return Promise.resolve(tracker);
      }
    }
  7. Implement Throttling for WebSockets

    master

    To use rate limiting with WebSockets, extend ThrottlerGuard and override the handleRequest method.

    Important:

    • WebSockets guards cannot be registered via APP_GUARD or app.useGlobalGuards().
    • You must listen for the exception event emitted by Nest when a limit is reached.
    • If using the ws package, replace _socket with conn.
    @Injectable
    export class WsThrottlerGuard extends ThrottlerGuard {
      async handleRequest(requestProps: ThrottlerRequest): Promise<boolean> {
        const { context, limit, ttl, throttler, blockDuration, generateKey } = requestProps;
    
        const client = context.switchToWs().getClient();
        const tracker = client._socket.remoteAddress;
        const key = generateKey(context, tracker, throttler.name);
        const { totalHits, timeToExpire, isBlocked, timeToBlockExpire } =
          await this.storageService.increment(key, ttl, limit, blockDuration, throttler.name);
    
        if (isBlocked) {
          await this.throwThrottlingException(context, {
            limit, ttl, key, tracker, totalHits, timeToExpire, isBlocked, timeToBlockExpire,
          });
        }
    
        return true;
      }
    }
  8. Define multiple named throttlers

    master

    You can set up multiple throttling definitions (e.g., different limits for different time windows) by providing an array of objects with a name property. These names allow you to reference specific throttler sets later using decorators.

    @Module({
      imports: [
        ThrottlerModule.forRoot([
          {
            name: 'short',
            ttl: 1000,
            limit: 3,
          },
          {
            name: 'medium',
            ttl: 10000,
            limit: 20
          },
          {
            name: 'long',
            ttl: 60000,
            limit: 100
          }
        ]),
      ],
    })
    export class AppModule {}
  9. Use Time Helpers for readability

    master

    Instead of calculating milliseconds manually, use the exported time helpers to define ttl values: seconds(n), minutes(n), hours(n), days(n), and weeks(n).

    // Example usage
    ThrottlerModule.forRoot([
      { 
        ttl: minutes(5), 
        limit: 10 
      }
    ])
  10. Use @Throttle() to override limits

    master

    The @Throttle() decorator allows you to override the limit and ttl for specific throttlers on a per-controller or per-route basis. When using named throttlers, you must pass an object where keys are the throttler names and values are objects containing limit and ttl.

    // Override default configuration
    @Throttle({ default: { limit: 3, ttl: 60000 } })
    @Get()
    findAll() {
      return "Custom limits applied";
    }
    
    // Override specific named throttlers
    @Throttle({ short: { limit: 5, ttl: 1000 }, medium: { limit: 30, ttl: 10000 } })
    @Get()
    findAllCustom() {
      return "Custom limits for specific throttlers";
    }
  11. Use @SkipThrottle() with named throttlers

    master

    When using named throttlers, the @SkipThrottle() decorator requires an object where keys are the names of the throttlers you wish to skip and values are true.

    Warning: Calling @SkipThrottle() without arguments will not skip any named throttlers.

    // To skip specific named throttlers
    @SkipThrottle({ short: true, medium: true })
    @Controller('users')
    export class UsersController {}
    
    // To negate skipping for a specific route in a skipped class
    @SkipThrottle({ default: false })
    @Get()
    undoSkip() {
      return 'Rate limiting is applied here.';
    }