A rate limiter is a named closure, defined with RateLimiter::for() in AppServiceProvider::boot(), that returns a Limit: perSecond, perMinute, perMinutes, perHour, perDay or none(). by() sets the bucket key, and the throttle:name middleware attaches it. The bookshop allows each IP three searches a minute:
RateLimiter::for('search', function (Request $request) {
return Limit::perMinute(3)->by($request->ip());
});
Route::get('/search/{terms}', fn (string $terms) => "Results for: {$terms}")
->where('terms', '.*')->middleware('throttle:search');Output
$ for i in 1 2 3 4; do
curl -si -H 'Accept: application/json' 'http://127.0.0.1:8303/search/php%20books' \
| grep -E '^(HTTP|X-RateLimit|Retry-After)|^Results|message'
done
HTTP/1.1 200 OK
X-RateLimit-Limit: 3
X-RateLimit-Remaining: 2
Results for: php books
...
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 3
X-RateLimit-Remaining: 0
Retry-After: 60
X-RateLimit-Reset: 1790148021
"message": "Too Many Attempts.",Counters live in the default cache store (the cache table here); throttleWithRedis() in withMiddleware() moves them to Redis 2,763 . Key signed-in users by ID, by($request->user()?->id ?: $request->ip()), so an office behind one NAT address is not throttled as one. Return an array of limits for layered rules, response() customizes the 429, and after() counts only chosen responses, such as 404s from someone enumerating order numbers.