> ## Documentation Index
> Fetch the complete documentation index at: https://nestrs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Throttling — `nestrs-throttle` crate

> Per-route rate limits with pluggable key generators, skipper, and backend (in-memory or Redis) — the @nestjs/throttler analogue.

Rate limiting lives in the `nestrs-throttle` workspace member. It is
the in-tree analogue of `@nestjs/throttler`: a `ThrottlerGuard` for
declarative per-route limits, a `throttler_middleware` that applies the
same skip → decorated → global precedence (via `RouteRegistry` lookup,
because app-level layers cannot read `HandlerKey`), a `ThrottlerService`
for manual checks from inside a handler,
and pluggable storage (`InMemoryThrottler` for single-instance,
`RedisThrottler` for distributed).

`ThrottleSpec` is `{ limit, window_secs }`. Parse decorator strings with
`ThrottleSpec::parse("5/minute")` (`second` / `minute` / `hour` only).
There is **no** `ThrottlerOptions::builder()`, **no** `window_ms` field,
and **no** `#[Throttle]` PascalCase attribute.

## Define a spec, register the module

```rust theme={null}
use nestrs_throttle::{
    ThrottleSpec, ThrottlerModule, ThrottlerOptions, ThrottlerBackendKind,
};
use nestrs::core::Module;

#[module(imports = [ThrottlerModule::register(ThrottlerOptions {
    backend: ThrottlerBackendKind::InMemory,
    global: ThrottleSpec::parse("60/minute"),
    ..Default::default()
})])]
pub struct AppModule;
```

The typical application path is `NestApplication::use_throttler` (feature
`throttler`), which installs the 429 middleware and registers
`ThrottlerService` for the guard:

```rust theme={null}
use nestrs::prelude::*;
use nestrs_throttle::{ThrottleSpec, ThrottlerOptions};

NestFactory::create::<AppModule>()
    .use_throttler(ThrottlerOptions {
        global: ThrottleSpec::parse("100/minute"),
        ..ThrottlerOptions::default()
    })
    .listen(3000)
    .await;
```

`ThrottlerOptions.global` is the fallback for routes without
`#[throttle(...)]`. `None` (the default) means **only** explicitly
decorated routes are throttled.

## Per-route limits

```rust theme={null}
use nestrs::prelude::*;

#[controller("/users", prefix = "/api")]
pub struct UsersController;

#[routes(state = AppState)]
impl UsersController {
    #[get("/:id")]
    #[throttle(5, "minute")]
    pub async fn show(/* … */) { /* … */ }

    #[get("/health")]
    #[skip_throttle]
    pub async fn health() -> &'static str { "ok" }
}
```

`#[throttle(n, "second" | "minute" | "hour")]` records metadata
`"throttle" => "n/per"` and **overrides** the module global for that
handler. `#[skip_throttle]` records `"skip_throttle" => "true"` and
exempts the route entirely (wins over both the decorator and the
global). One of each per handler — they do not stack into multiple
windows.

Rejected requests answer `429` with `Retry-After` and
`X-RateLimit-Remaining`.

## Manual checks

When a rate-limit decision depends on something the module can't see
(resource ownership, tenant tier), reach for the service directly.
`ThrottlerService::check` takes `(handler, key, spec)` and scopes the
backend key as `{handler}:{key}`:

```rust theme={null}
use nestrs_throttle::{ThrottlerService, ThrottleOutcome, ThrottleSpec};

pub struct ApiKeyAuth {
    throttler: ThrottlerService,
}

impl ApiKeyAuth {
    pub async fn validate(&self, key: &str) -> Result<Principal, AuthError> {
        let spec = ThrottleSpec::parse("10/second").expect("valid spec");
        match self
            .throttler
            .check("ApiKeyAuth::validate", &format!("api_key:{key}"), &spec)
            .await
        {
            ThrottleOutcome::Limited { retry_after_secs: _ } => {
                return Err(AuthError::TooManyRequests);
            }
            ThrottleOutcome::Allowed { remaining: _ } => {}
        }
        /* ... look up the principal ... */
    }
}
```

The key string is whatever you want it to be — the service doesn't care
whether you derive it from an IP, a user id, or a hash of the request
body.

## Custom key generators

The default key is the client IP (resolved via the same trusted-proxy
helpers as `ClientIp`). To rate-limit by API key instead, set
`ThrottlerOptions.key_generator`:

```rust theme={null}
use std::sync::Arc;
use nestrs_throttle::{
    ApiKeyHeaderKeyGenerator, ThrottlerModule, ThrottlerOptions,
};

#[module(imports = [ThrottlerModule::register(ThrottlerOptions {
    key_generator: Some(Arc::new(ApiKeyHeaderKeyGenerator::default())),
    ..Default::default()
})])]
pub struct AppModule;
```

`ApiKeyHeaderKeyGenerator::default()` reads `x-api-key`;
`ApiKeyHeaderKeyGenerator::new(header)` overrides the name. Missing
headers fall back to the IP so unkeyed traffic is still throttled.
`PrincipalKeyGenerator` reads the `PrincipalId` request extension
installed by `authn` / `oauth2`.

`ThrottleKeyGenerator` is a trait over `ThrottlerRequest<'_>` —
`handler`, `ip`, and borrowed `parts` (headers, URI, extensions). There
is no `req.path` field; use `req.parts.uri.path()`.

## Skip on demand

Internal services, health-check endpoints, and authenticated admins
typically shouldn't count against the limit. Implement `ThrottleSkipper`:

```rust theme={null}
use nestrs_throttle::{ThrottleSkipper, ThrottlerRequest};

pub struct SkipAdminRoutes;

impl ThrottleSkipper for SkipAdminRoutes {
    fn skip(&self, req: &ThrottlerRequest<'_>) -> bool {
        req.parts.uri.path().starts_with("/admin/")
            || req.parts.headers.get("x-internal-token").is_some()
    }
}
```

Install it as `ThrottlerOptions { skipper: Some(Arc::new(SkipAdminRoutes)), .. }`.
`NeverSkip` is the default. Skipped requests neither consume counters
nor generate blocks.

## Distributed limits

Enable `throttler-redis` on the umbrella (maps to
`nestrs-throttle/cache-redis`):

```rust theme={null}
ThrottlerOptions {
    backend: ThrottlerBackendKind::Redis {
        url: "redis://10.0.0.1:6379".into(),
        key_prefix: "throttle:".into(),
    },
    global: ThrottleSpec::parse("1000/minute"),
    ..Default::default()
}
```

`RedisThrottler` uses an atomic INCR + EXPIRE pattern per spec key, so
concurrent processes see the same counter. Connection failure at init
falls back to in-memory (throttling is an optimization, not an
availability gate). Sliding-window and token-bucket are not built in.
`Debug` redacts `user:pass@` from the Redis URL.

## When to use what

* **`#[throttle(n, "per")]` on a route** — the default. Limit and
  window are explicit and visible.
* **`#[skip_throttle]`** — probes, admin, and internal paths. Raise
  limits on "important" routes; don't skip them.
* **`ThrottlerService::check` from inside a handler** — when the
  limit depends on handler-side state (per-tenant, per-resource).
* **`ThrottleSkipper`** — bypass for a class of requests the decorator
  can't see (internal IPs, a header).
* **Custom `ThrottleKeyGenerator`** — when the IP-derived key is
  wrong for your traffic shape (NAT'd mobile clients, single egress
  proxy, per-tenant SaaS).

## See also

* [Guides: authorization](/guides/authorization) — for combining
  auth principal with throttling key generation.
* [Guides: security](/guides/security) — global `use_rate_limit` vs
  per-route `use_throttler`.
* [Concepts: middleware-pipeline](/concepts/middleware-pipeline) —
  the global `throttler_middleware` runs before handlers.
* [`@nestjs/throttler`](https://docs.nestjs.com/security/rate-limiting)
  — upstream documentation for comparison.
