Socket
Socket
Sign inDemoInstall

@nestjs/throttler

Package Overview
Dependencies
213
Maintainers
2
Versions
20
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    @nestjs/throttler

A Rate-Limiting module for NestJS to work on Express, Fastify, Websockets, Socket.IO, and GraphQL, all rolled up into a simple package.


Version published
Weekly downloads
291K
increased by4.69%
Maintainers
2
Created
Weekly downloads
 

Changelog

Source

4.2.0

Minor Changes

  • d8d8c93: Allow for ttl and limit to be set based on the execution context, instead of statically assigned for the entire application

Readme

Source

Nest Logo

A progressive Node.js framework for building efficient and scalable server-side applications.

NPM Version Package License NPM Downloads Travis Linux Coverage Discord Backers on Open Collective Sponsors on Open Collective

Description

A Rate-Limiter for NestJS, regardless of the context.

For an overview of the community storage providers, see Community Storage Providers.

This package comes with a couple of goodies that should be mentioned, first is the ThrottlerModule.

Installation

$ npm i --save @nestjs/throttler

Versions

@nestjs/throttler@^1 is compatible with Nest v7 while @nestjs/throttler@^2 is compatible with Nest v7 and Nest v8, but it is suggested to be used with only v8 in case of breaking changes against v7 that are unseen.

For NestJS v10, please use version 4.1.0 or above

Table of Contents

Usage

ThrottlerModule

The ThrottleModule is the main entry point for this package, and can be used in a synchronous or asynchronous manner. All the needs to be passed is the ttl, the time to live in seconds for the request tracker, and the limit, or how many times an endpoint can be hit before returning a 429.

import { APP_GUARD } from '@nestjs/core';
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';

@Module({
  imports: [
    ThrottlerModule.forRoot({
      ttl: 60,
      limit: 10,
    }),
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: ThrottlerGuard,
    },
  ],
})
export class AppModule {}

The above would mean that 10 requests from the same IP can be made to a single endpoint in 1 minute.

@Module({
  imports: [
    ThrottlerModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        ttl: config.get('THROTTLE_TTL'),
        limit: config.get('THROTTLE_LIMIT'),
      }),
    }),
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: ThrottlerGuard,
    },
  ],
})
export class AppModule {}

The above is also a valid configuration for asynchronous registration of the module.

NOTE: If you add the ThrottlerGuard to your AppModule as a global guard then all the incoming requests will be throttled by default. This can also be omitted in favor of @UseGuards(ThrottlerGuard). The global guard check can be skipped using the @SkipThrottle() decorator mentioned later.

Example with @UseGuards(ThrottlerGuard):

// app.module.ts
@Module({
  imports: [
    ThrottlerModule.forRoot({
      ttl: 60,
      limit: 10,
    }),
  ],
})
export class AppModule {}

// app.controller.ts
@Controller()
export class AppController {
  @UseGuards(ThrottlerGuard)
  @Throttle(5, 30)
  normal() {}
}

Decorators

@Throttle()
@Throttle(limit: number = 30, ttl: number = 60)

This decorator will set THROTTLER_LIMIT and THROTTLER_TTL metadatas on the route, for retrieval from the Reflector class. Can be applied to controllers and routes.

@SkipThrottle()
@SkipThrottle(skip = true)

This decorator can be used to skip a route or a class or to negate the skipping of a route in a class that is skipped.

@SkipThrottle()
@Controller()
export class AppController {
  @SkipThrottle(false)
  dontSkip() {}

  doSkip() {}
}

In the above controller, dontSkip would be counted against and rate-limited while doSkip would not be limited in any way.

Ignoring specific user agents

You can use the ignoreUserAgents key to ignore specific user agents.

@Module({
  imports: [
    ThrottlerModule.forRoot({
      ttl: 60,
      limit: 10,
      ignoreUserAgents: [
        // Don't throttle request that have 'googlebot' defined in them.
        // Example user agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
        /googlebot/gi,

        // Don't throttle request that have 'bingbot' defined in them.
        // Example user agent: Mozilla/5.0 (compatible; Bingbot/2.0; +http://www.bing.com/bingbot.htm)
        new RegExp('bingbot', 'gi'),
      ],
    }),
  ],
})
export class AppModule {}

Setting contextual ttl or limit

You can specify the ttl or limit based on request's information using a callback function instead of a value:

@Module({
  imports: [
    ThrottlerModule.forRoot({
      ttl: 60,
      limit: (context) => context.swithToHttp().getRequest().ip === "some-ip" ? 1000 : 10,
    })
  ]
})

The same is available for ttl, and on the Throttle decorator.

The callback accepts a Promise if you need an asynchronous operation to retrieve the value.

ThrottlerStorage

Interface to define the methods to handle the details when it comes to keeping track of the requests.

Currently the key is seen as an MD5 hash of the IP the ClassName and the MethodName, to ensure that no unsafe characters are used and to ensure that the package works for contexts that don't have explicit routes (like Websockets and GraphQL).

The interface looks like this:

export interface ThrottlerStorage {
  storage: Record<string, ThrottlerStorageOptions>;
  increment(key: string, ttl: number): Promise<ThrottlerStorageRecord>;
}

So long as the Storage service implements this interface, it should be usable by the ThrottlerGuard.

Proxies

If you are working behind a proxy, check the specific HTTP adapter options (express and fastify) for the trust proxy option and enable it. Doing so will allow you to get the original IP address from the X-Forward-For header, and you can override the getTracker() method to pull the value from the header rather than from req.ip. The following example works with both express and fastify:

// 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>): string {
    return req.ips.length ? req.ips[0] : req.ip; // individualize IP extraction to meet your own needs
  }
}

// app.controller.ts
import { ThrottlerBehindProxyGuard } from './throttler-behind-proxy.guard';
@UseGuards(ThrottlerBehindProxyGuard)

Working with Websockets

To work with Websockets you can extend the ThrottlerGuard and override the handleRequest method with something like the following method

@Injectable()
export class WsThrottlerGuard extends ThrottlerGuard {
  async handleRequest(context: ExecutionContext, limit: number, ttl: number): Promise<boolean> {
    const client = context.switchToWs().getClient();
    // this is a generic method to switch between `ws` and `socket.io`. You can choose what is appropriate for you
    const ip = ['conn', '_socket']
      .map((key) => client[key])
      .filter((obj) => obj)
      .shift().remoteAddress;
    const key = this.generateKey(context, ip);
    const { totalHits } = await this.storageService.increment(key, ttl);

    if (totalHits > limit) {
      throw new ThrottlerException();
    }

    return true;
  }
}

There are some things to take keep in mind when working with websockets:

  • You cannot bind the guard with APP_GUARD or app.useGlobalGuards() due to how Nest binds global guards.
  • When a limit is reached, Nest will emit an exception event, so make sure there is a listener ready for this.

Working with GraphQL

To get the ThrottlerModule to work with the GraphQL context, a couple of things must happen.

  • You must use Express and apollo-server-express as your GraphQL server engine. This is the default for Nest, but the apollo-server-fastify package does not currently support passing res to the context, meaning headers cannot be properly set.
  • When configuring your GraphQLModule, you need to pass an option for context in the form of ({ req, res}) => ({ req, res }). This will allow access to the Express Request and Response objects, allowing for the reading and writing of headers.
  • You must add in some additional context switching to get the ExecutionContext to pass back values correctly (or you can override the method entirely)
@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 }; // ctx.request and ctx.reply for fastify
  }
}

Community Storage Providers

Feel free to submit a PR with your custom storage provider being added to this list.

License

Nest is MIT licensed.

Keywords

FAQs

Last updated on 06 Jul 2023

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc