# @nestjs/throttler rate limits by endpoint, with named policies and per route overrides

> The Throttler module for NestJS enforces a request limit and a time to live per endpoint, identifies callers by IP unless you supply your own tracker, and lets you define named policies that decorators can target. The sharp edges are in the configuration: an empty forRoot array disables everything while still looking configured, and named throttlers need names in every decorator.

**nestjs/throttler** — A rate limiting module for NestJS to work with Fastify, Express, GQL, Websockets, and RPC 🧭 

- Repository: https://github.com/nestjs/throttler
- Website: https://nestjs.com
- Stars: 711 · Forks: 90
- Language: TypeScript
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/nestjs-throttler

## One limiter for every transport Nest speaks

The module is described as a rate limiter for NestJS, regardless of the context, and the package description spells out what that covers: Express, Fastify, Websockets, Socket.IO and GraphQL, all rolled up into a simple package.

The contract itself is small. Throttler ensures that users can only make `limit` requests per `ttl` to each endpoint, where the limit is the maximum number of requests within the time to live, and the ttl is expressed in milliseconds. The default identity of a user is their IP address, and that behaviour is customizable by providing your own `getTracker` function, which the README points at the proxies section for a case where that is useful, since behind a proxy the address you see is the proxy's.

The state lives in a built-in in-memory cache that keeps track of the requests, and the module supports alternate storage providers, with an overview of community storage providers linked from the README. That is the shape of the whole design: a guard, a policy object, a tracker function and a storage adapter, with the last of those being the part you will replace first if you run more than one instance.

The project is MIT licensed, the repository lives under the nestjs organisation, and the version in the manifest is 6.7.1.

## Installing and binding the guard

The package installs like any other Nest dependency:

```bash
$ npm i --save @nestjs/throttler
```

After installation the `ThrottlerModule` can be configured as any other Nest package with the `forRoot` or `forRootAsync` methods. The basic configuration sets the global options for the ttl and the limit for the routes of your application that are guarded:

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

Once the module is imported, you choose how to bind the `ThrottlerGuard`, and any binding style from the guards section of the Nest documentation works. To bind it globally, add the provider to any module:

```ts
{
  provide: APP_GUARD,
  useClass: ThrottlerGuard
}
```

The two decisions in that snippet, global binding and a single unnamed policy, are the ones that determine how the rest of the application behaves, and the README is unusually clear about the difference between the two binding strategies, which is covered further below.

The `@@filename(app.module)` marker in the snippet is a documentation convention for showing which file the code belongs in, since the same module appears in several examples.

## Named throttlers, and the decorator that silently does nothing

Different routes need different budgets, and the module expresses that with named definitions. The README's example is three tiers, no more than 3 calls in a second, 20 calls in 10 seconds, and 100 calls in a minute:

```ts
@@filename(app.module)
@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 {}
```

Those names are then referenced by the `@SkipThrottle()` and `@Throttle()` decorators. The README is emphatic about a failure mode here. When you have configured named throttlers, both decorators must be provided with an object where the keys are the names of your throttlers. If you use `@SkipThrottle()` without specifying names, it will not skip any of your named throttlers, and similarly `@Throttle()` without specifying names cannot override settings for specific named throttlers.

The correct form passes the names:

```ts
@SkipThrottle({ short: true, medium: true })
@Controller('users')
export class UsersController {}
```

And the incorrect form is documented just as explicitly:

```ts
@SkipThrottle() // This will NOT skip any named throttlers
@Controller('users')
export class UsersController {}
```

The failure is quiet rather than loud, since the decorator compiles and runs and simply has no effect, and the README points to the migration to v5 guide for the behaviour change, especially when migrating from older versions.

## Global binding and route binding protect opposite things

The README spends a paragraph on a distinction that is easy to get wrong. The `ThrottlerModule` is global, so it is imported once, and to limit a subset of routes you leave out the `APP_GUARD` provider and bind the guard to the controller or the handler instead. The guard is passed as a class, so dependency injection keeps working:

```ts
@UseGuards(ThrottlerGuard)
@Controller('reports')
export class ReportsController {}
```

The consequence is spelled out: binding the guard globally and skipping the routes that should not be limited keeps every route limited by default, including the ones added later. Binding it per route inverts that, so a route without the guard carries no limit.

That is a security posture decision, not a style choice. Global binding means the default state of a new controller is protected, and the burden falls on the developer to remember to exempt an endpoint. Per route binding means the default state is unprotected, and the burden is on the developer to remember to protect it. The second is a common source of an endpoint that was never rate limited because nobody was looking.

The per route form also composes with named policies. To leave a policy out for one route, skip it by name:

```ts
@SkipThrottle({ long: true })
@UseGuards(ThrottlerGuard)
@Post('login')
login() {
  return this.authService.login();
}
```

And the README clarifies the semantics of the definitions array: it is one policy, not a menu, every entry applies wherever the guard is bound, and `@Throttle()` changes only the fields it lists, so `@Throttle({ default: { limit: 5 } })` lowers the limit and keeps the ttl from the configuration.

## An empty configuration looks like a configured one

There is one trap in the configuration that deserves its own attention, and the README states it plainly. Every name used in a decorator must exist in the module configuration. `ThrottlerModule.forRoot()` and `ThrottlerModule.forRoot([])` register no throttler at all, so the guard has nothing to check and every request passes. The guard reports this with a warning when the application starts.

A warning at startup is a thin line of defence for a failure that disables rate limiting completely. Nothing throws, no request is rejected, and the only symptom is a log line that is easy to miss among the other startup output. A test that asserts a request beyond the limit is rejected would catch it; a smoke test that only checks the endpoints respond would not.

The same rule applies to the names themselves. A decorator naming a throttler that was never configured does not fall back to another policy, it simply has no policy to apply, and the request passes. The safest configuration is one where every name in the module array is used somewhere and every name in a decorator exists in the array.

The `@@filename` markers and the explicit names in the README examples are the documentation's way of making that mapping visible, and they are worth copying into your own codebase when you set up several tiers.

## Where the request counter is kept

Throttler comes with a built-in in-memory cache to keep track of the requests, and it supports alternate storage providers, with an overview of community storage providers linked from the README.

The in-memory default is the right default for a single process and the wrong default for a scaled deployment, and this is the design decision that most affects whether the module protects you. A counter held in the memory of one Node process counts only the requests that reached that process. Behind a load balancer with several instances, the effective limit is a multiple of what you configured, because each instance keeps its own count, and a restart clears it.

The README does not document the behaviour across multiple application instances, so if you run more than one, the storage adapter is the part you have to reason about yourself. The community storage providers are where you look for a shared counter, and swapping the storage is the documented extension point rather than something you build around the guard.

The other extension point is the tracker. The default identifies a user by IP address, and providing your own `getTracker` function changes that. The README points at the proxies section, which is the case where the default is wrong: behind a reverse proxy, the address on the request is the proxy's, so every client shares one bucket unless the real client address is recovered. The customization section that follows in the README also covers disabling rate limiting for one or more controllers or routes, though the excerpt of that section is cut off in the README file itself.

## Version compatibility and the maintenance surface

The module has a version history that maps onto NestJS versions, and the README keeps an explicit table of it. `@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, the README says to use version 4.1.0 or above. The current release line is 6.x, with tags v6.7.1 from 2026-09-24, v6.7.0 from 2026-09-17 and v6.6.0 from 2026-09-16, and the last push was on 2026-10-18.

The manifest also pins the runtime. The `engines` field requires Node `^20.19.0 || ^22.12.0 || >=24.0.0`, so an older Node build in your CI is a problem before any code is written, and there is a `.nvmrc` in the repository for the same reason.

The rest of the repository describes a well-run project. Tests run under Vitest with a separate end to end configuration, `vitest.config.e2e.mts`, alongside the main `vitest.config.mts`, and there is coverage and watch modes. Formatting is Prettier over the source and test directories, linting is `oxlint`, and the build is `nest build`. Releases go through changesets, with a `changeset publish` script, and the repository carries a `SECURITY.md`, a `CODE_OF_CONDUCT.md`, `CONTRIBUTING.md`, a `CHANGELOG.md`, a `renovate.json` for dependency updates and a `pnpm-workspace.yaml`.

## Conclusion

Adopt @nestjs/throttler when you run a NestJS service that needs per endpoint request limits, especially when different routes deserve different budgets, and when you will bind the guard globally so new routes are limited by default. Do not adopt it as a distributed quota system on its own, since the built-in storage is an in-memory cache and the README points to community providers for that. Verify first that the `forRoot` array is not empty, because `ThrottlerModule.forRoot()` and `forRoot([])` register no throttler and every request passes with only a startup warning, that every name used in `@Throttle()` and `@SkipThrottle()` exists in the module configuration, and that your Node version satisfies the engines field, which requires 20.19.0, 22.12.0 or 24 and above.

## FAQ

### How do I install and configure the NestJS throttler?

Install it with `npm i --save @nestjs/throttler`, then configure `ThrottlerModule.forRoot([{ ttl: 60000, limit: 10 }])` in your module and bind the `ThrottlerGuard` globally with the `APP_GUARD` provider, or bind it to individual controllers and handlers with `@UseGuards(ThrottlerGuard)`.

### Why does my @SkipThrottle() decorator not skip anything?

With named throttlers configured, the decorators must be given an object whose keys are the throttler names, for example `@SkipThrottle({ short: true, medium: true })`. Without names, `@SkipThrottle()` will not skip any of your named throttlers, and `@Throttle()` cannot override settings for specific named throttlers.

### What happens if ThrottlerModule.forRoot([]) is used?

Both `ThrottlerModule.forRoot()` and `ThrottlerModule.forRoot([])` register no throttler at all, so the guard has nothing to check and every request passes. The guard reports this with a warning when the application starts.

### How does the NestJS throttler identify a user?

By default users are identified by their IP address. You can change that by providing your own `getTracker` function, and the README points to its proxies section as the case where this matters, such as when the application sits behind a reverse proxy.

## Sources

- [License: MIT](https://github.com/nestjs/throttler/blob/master/LICENSE)
- [nestjs/throttler on GitHub](https://github.com/nestjs/throttler)
- [Project website](https://nestjs.com)
- [README](https://github.com/nestjs/throttler/blob/master/README.md)
- [Releases](https://github.com/nestjs/throttler/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nestjs-throttler
