Introduction

Rate limiting protects your app from abuse: a brute-force login, a runaway script, or a cheap-but-popular public endpoint hammered by bots. Laravel bundles a flexible rate limiter you can apply to any route with one call.

Key Concepts

  • throttle middleware: Limits how many requests a given client can make in a time window.
  • Named limiter: A reusable rate limit defined in a service provider with RateLimiter::for().
  • Key resolver: How Laravel identifies 'the same client' — usually the authenticated user ID or the IP address.
  • Limit::perMinute(n)->by(...): The fluent API that builds a single limit bucket.

Real World Context

Every public login form should be rate limited — without it, attackers can try thousands of passwords per second. APIs use rate limits to enforce fair usage across customers and protect upstream services.

Deep Dive

The simplest form hard-codes the limit on the route:

php
// 60 requests per 1 minute, keyed by the client automatically
Route::get('/api/data', DataController::class)
    ->middleware('throttle:60,1');

Notice how concise the inline form is. For anything reusable, define a named limiter in App\Providers\AppServiceProvider::boot():

php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

public function boot(): void
{
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60)
            ->by($request->user()?->id ?: $request->ip());
    });

    RateLimiter::for('uploads', function (Request $request) {
        return $request->user()?->isPremium()
            ? Limit::none()
            : Limit::perMinute(10)->by($request->user()->id);
    });
}

Now any route can reference the limiter by name:

php
Route::middleware('throttle:api')->group(function () {
    Route::get('/api/posts', [PostController::class, 'index']);
});

When a client exceeds the limit, Laravel responds with HTTP 429 Too Many Requests and includes Retry-After, X-RateLimit-Limit, and X-RateLimit-Remaining headers so well-behaved clients can back off.

Common Pitfalls

  1. Keying only by IP behind a proxy — If your app runs behind Cloudflare or an ALB, every request looks like it comes from the proxy. Configure TrustProxies middleware so $request->ip() returns the real client.
  2. Forgetting premium users — Rate limits should bypass trusted partners. Return Limit::none() for accounts that should not be throttled.

Best Practices

  1. Name your limiters — Inline throttle:60,1 works but scatters policy through your routes. Named limiters centralise the rules.
  2. Key by user when authenticated, IP when not — Anonymous traffic shares a pool; authenticated traffic deserves its own budget.

Summary

  • Apply throttle:max,minutes for quick inline limits.
  • Define RateLimiter::for('name', ...) for reusable rules.
  • Key limits by user()->id ?: ip() so authenticated users get their own budget.
  • Laravel returns HTTP 429 with rate-limit headers when the limit is exceeded.

Code Examples

php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

// In AppServiceProvider::boot()
RateLimiter::for('login', function (Request $request) {
    return Limit::perMinute(5)
        ->by($request->input('email') . '|' . $request->ip());
});

// In routes/web.php
Route::post('/login', [LoginController::class, 'store'])
    ->middleware('throttle:login');
✓ Completed