LogoRevali

Throttle

Reject callers that send too many requests

@Throttle rejects a caller that exceeds a request allowance with 429 Too Many Requests. Apply it to an app, a controller, or a single endpoint:

@Throttle(max: 100, window: Duration(minutes: 1))
@Controller('search')
class SearchController {
  const SearchController();

  @Get()
  Future<List<Result>> search(@Query() String q) => _search(q);
}

What counts as one caller#

The client IP, resolved through AppConfig.trustedProxy — so behind a proxy or load balancer it counts the real client rather than the proxy that forwarded every request.

What counts as one allowance#

By default, the matched route — its registered path, not the concrete URL. /api/users/:id is one bucket, so a caller hitting /api/users/1 and /api/users/2 spends one allowance, not two.

Set bucket to pool several endpoints under a shared allowance, which is what you usually want for something like sign-in:

@Throttle(max: 5, window: Duration(minutes: 15), bucket: 'auth')
@Post('login')
Future<Session> login(@Body() Credentials body) => _login(body);

@Throttle(max: 5, window: Duration(minutes: 15), bucket: 'auth')
@Post('reset-password')
Future<void> reset(@Body() Email body) => _reset(body);

The rejection#

A blocked request gets 429 with:

HeaderMeaning
Retry-AfterSeconds until the allowance resets. Never 0
X-RateLimit-LimitThe configured max
X-RateLimit-Remaining0, since the caller is over

Two limits worth knowing before you rely on it#

What's next?#