Rate Limiting
Rate Limiting
Hazaar includes a flexible rate limiting system that can be used directly in your code with Hazaar\Util\RateLimiter or attached to routes through the Hazaar\Controller\Middleware\Ratelimiter middleware. This allows you to throttle requests by IP address, user ID, API key, or any identifier you choose.
Using the RateLimiter Class
Use the Hazaar\Util\RateLimiter class when you need full control over identifiers, custom backends, or want to inspect rate limit state.
use Hazaar\Util\RateLimiter;
$limiter = new RateLimiter([
'limit' => 60, // Max requests
'window' => 60, // Window length in seconds
'minimum' => 0, // Minimum seconds between requests (optional)
'backend' => 'cache',
'backendOptions' => [
'type' => 'shm'
],
]);
$identifier = $request->getRemoteAddr();
if (!$limiter->check($identifier)) {
return $this->response->tooManyRequests();
}Options
limit(int): Maximum number of requests allowed within the window.window(int): Window length in seconds.minimum(int): Minimum time in seconds between requests for the same identifier.backend(string): Backend type:cache,file, ordbi.backendOptions(array): Backend-specific options.
Common Methods
check(string $identifier): bool- Returnstrueif the request is allowed, and saves it to the log straight away. A refused request isn't logged.getInfo(string $identifier): array- Returns rate limit state including attempts and remaining requests.getHeaders(string $identifier): array- Returns headers such asX-RateLimit-Remaining.delete(string $identifier): void- Clears the stored rate limit state for an identifier.
Identifiers
Rate limiting is scoped by identifier. Typical choices include:
- Remote IP address (
$request->getRemoteAddr()) - Authenticated user ID
- API key or token
Pick an identifier that matches your security model and traffic patterns.
Middleware Usage
If you only need standard behavior, use the rate limiter middleware. It uses the default cache backend configured in your app settings.
You can attach it directly using the router or via middleware():
use Hazaar\Application\Router;
use Hazaar\Controller\Middleware\Ratelimiter;
Router::get('/api/user', [Api::class, 'user'])
->middleware(new Ratelimiter(60, 60));Hazaar also provides a convenience helper on routes:
Router::get('/api/user', [Api::class, 'user'])
->ratelimit(60, 60);The middleware returns a RateLimitExceeded (HTTP 429) response when the limit is exceeded.
Each ratelimit() route has its own budget, counted per client address (see Behind a Reverse Proxy for how the address is found). The budget is keyed on the route's methods and path pattern, so /user/{id} is one budget for every ID, and requests to /login don't count against /register.
When you add the middleware yourself, pass a bucket to share or separate budgets. Without one, the request path is used:
// Both routes share one budget of 10 requests a minute per client.
Router::post('/login', [Auth::class, 'login'])->middleware(new Ratelimiter(10, 60, bucket: 'auth'));
Router::post('/reset', [Auth::class, 'reset'])->middleware(new Ratelimiter(10, 60, bucket: 'auth'));The limiter and its storage aren't created until the first request reaches the route, so a cache backend that isn't available only affects rate-limited requests, not route loading.
Configuring Cache Defaults (app.php)
The rate limiter middleware uses the default cache backend configured for your application. You can set this in app.php under the cache key. These settings are used by the cache-backed rate limiter when no explicit backend is provided.
return [
'production' => [
'cache' => [
'backend' => 'file',
'options' => [
'ttl' => 3600,
],
],
],
];These cache defaults are applied automatically when you use ->ratelimit() or the Ratelimiter middleware without custom backend options. With no cache.backend configured, the limiter uses shm when the sysvshm and sysvsem extensions are available, and the file backend otherwise. It never uses the session backend, because a client can dodge a per-session limit by dropping its cookie.
Multiple servers
The limit is shared by every process that uses the same cache. With shm or file that's every process on one server, including long-running workers such as FrankenPHP worker mode. To share one limit across several servers, use redis or memcached.
Choosing an Approach
- Use
Hazaar\Util\RateLimiterdirectly when you need custom identifiers, headers, or backends. - Use
Hazaar\Controller\Middleware\Ratelimiter(orratelimit()) for simple per-route throttling with default cache configuration.