Back to Blog
Lesson 27 of the Intermediate Laravel: Real-World Application Patterns course
LaravelJune 26, 20264 min read

Rate Limiting API Endpoints: Protecting Laravel Apps

Learn how to implement custom rate limiting in Laravel to secure your API endpoints, prevent abuse, and ensure fair resource usage for all users.

LaravelAPISecurityRate LimitingMiddlewarephpbackend

Previously in this course, we discussed implementing middleware for API security to verify user ownership of resources. While that protects who can access specific data, today we focus on how much data an individual user can access. Rate limiting is your primary defense against brute-force attacks, API scraping, and accidental resource exhaustion caused by runaway client scripts.

Understanding Rate Limiting from First Principles

At its core, rate limiting is a traffic-shaping mechanism. It restricts the number of requests a user can make to your API within a defined window of time. If a user exceeds this threshold, the server rejects their request with a 429 Too Many Requests status code.

In a production environment, you want to differentiate between anonymous traffic (often limited by IP address) and authenticated users (limited by their user ID). Laravel makes this trivial through the RateLimiter facade and built-in middleware.

Defining Custom Rate Limiters

Laravel registers rate limiters within the boot method of your App\Providers\RouteServiceProvider. By default, you'll see an api limiter configured to allow 60 requests per minute based on the user's IP.

For our project board, we might want to be more generous with authenticated users while staying strict with guests. Open app/Providers/RouteServiceProvider.php and define a custom limiter:

PHP
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

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

By using the by() method, we anchor the rate limit to a specific unique identifier. For guests, ip() is standard; for logged-in users, user()->id ensures that even if they switch networks, their total quota remains consistent.

Applying Middleware to Routes

Once defined, you apply these limiters to your routes using the throttle middleware. Laravel automatically references the name you provided in the RateLimiter::for() call.

In your routes/api.php file, update your project-related routes:

PHP
Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
    Route::get('/projects', [ProjectController::class, 'index']);
    Route::post('/projects', [ProjectController::class, 'store']);
});

This configuration ensures that every request hitting these endpoints is checked against the logic we defined in the RouteServiceProvider. If a user hits 1001 requests within a minute, Laravel will automatically throw an HttpResponseException with a 429 status.

Handling Throttled Requests

When a client hits the limit, they receive a 429 response. It is good practice to include the Retry-After header so legitimate clients know how long to wait.

While Laravel handles this automatically, you may want to customize the response. You can modify the render method in your app/Exceptions/Handler.php or, more simply, use the response() callback in your rate limiter:

PHP
RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(1000)->by($request->user()->id)->response(function () {
        return response()->json(['message' => 'Slow down! Too many requests.'], 429);
    });
});

Hands-on Exercise

  1. Define a strict limiter: In your RouteServiceProvider, create a new limiter named uploads that restricts users to 5 requests per minute.
  2. Apply to a route: Create a dummy route /api/upload and apply the throttle:uploads middleware to it.
  3. Test: Use Postman or curl to hit the endpoint 6 times in rapid succession and verify you receive the 429 error.

Common Pitfalls

  • Caching Driver: Laravel's rate limiting relies on your cache driver. If you use the array driver in production, rate limits will reset every time a request cycle ends, rendering them useless. Ensure you are using redis or database as your CACHE_STORE in .env.
  • Proxy Headers: If your app sits behind a load balancer (like AWS ELB or Cloudflare), the ip() method might return the load balancer's IP rather than the user's. Ensure your application is configured to trust proxies in app/Http/Middleware/TrustProxies.php.
  • Over-throttling: Setting limits too low can frustrate legitimate users. Always analyze your typical usage patterns before enforcing strict caps. For a deeper look at balancing these needs, consider the strategies discussed in Rate limiting API security: A Practical Guide for Node.js and Laravel.

Recap

We've moved beyond simple authorization by implementing traffic shaping. By defining custom limiters in the RouteServiceProvider and applying them via the throttle middleware, you can protect your API from abuse. Remember that robust security isn't just about blocking bad actors; it's about API Throttling: Adaptive Backoff Strategies for Resilient Systems to ensure your service remains available for everyone.

Up next: Eloquent Performance Optimization, where we'll learn how to detect and fix N+1 query problems in your API responses.

Similar Posts