LogoRevali

Throttle

Reject callers that send too many requests with 429 Too Many Requests.

@Throttle is a built-in guard that rejects a caller with 429 Too Many Requests once it goes over a request allowance. Apply it to an app, a controller, or a single endpoint.

import 'package:revali_router/revali_router.dart';

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

  @Get()
  Future<List<Result>> search(@Query() String q) => _search(q);
}
ParameterDefaultMeaning
max 60 Requests allowed per window. Must be greater than 0.
window Duration(minutes: 1) How long an allowance lasts
bucket The matched route A name for a shared allowance. See below.

What Counts as One Caller#

A caller is identified by client IP, resolved through AppConfig.trustedProxy. Behind a proxy or load balancer, this counts the real client instead of the proxy that forwarded every request. See Client IP.

What Counts as One Allowance#

By default, each matched route has its own allowance, keyed by its registered path rather than 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 share one allowance across several endpoints. This is usually what you 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 the body Too Many Requests and these headers:

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

Throttle is a guard, so it runs after all middleware, and the response follows the usual error response rules.

Limits#