When building APIs, one of the biggest challenges is preventing abuse and overloading your server with too many requests. Whether it’s a malicious bot or a misconfigured script, an uncontrolled request rate can degrade performance and even crash your app.
That’s where rate-limiting comes in — it helps you control how often a client can hit your API within a specific time frame.
In this post, I’ll walk through how to create a custom rate-limiting middleware in Laravel, understand how Laravel’s built-in system works, and explore best practices for implementing it efficiently.
What is Rate-Limiting?
Rate-limiting is the process of restricting how many times a user or client can make a request to your API within a given timeframe (e.g., 100 requests per minute).
It’s an essential part of modern API security and performance optimization. Without it, your API could face issues like:
- Denial of Service (DoS) from excessive requests
- Increased server load and latency
- Data abuse from unauthorized automation
⚙️ How Laravel Handles Rate-Limiting by Default
Laravel already includes a rate-limiting feature via its ThrottleRequests middleware, which works with API routes by default.
You can find it defined in your app/Http/Kernel.php file under the 'api' middleware group.
'api' => [
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
Laravel’s default API rate limiter allows 60 requests per minute per user/IP.
You can customize this behavior in the RouteServiceProvider:
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
protected function configureRateLimiting()
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
}
This configuration tells Laravel:
“Each user or IP can make up to 60 requests per minute.”
Creating a Custom Rate-Limiting Middleware
Now, let’s build our custom rate-limiting middleware from scratch.
This gives us more control — for example, we could apply different limits to different endpoints or user roles.
Step 1: Create the Middleware
Run this Artisan command:
php artisan make:middleware CustomRateLimiter
This creates a new file at app/Http/Middleware/CustomRateLimiter.php.
Step 2: Implement Rate-Limiting Logic
Edit the file and add the following code:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Cache;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CustomRateLimiter
{
protected $maxAttempts = 10; // max requests
protected $decaySeconds = 60; // time window in seconds
public function handle(Request $request, Closure $next)
{
$key = sprintf('rate_limit:%s', $request->ip());
$attempts = Cache::get($key, 0);
if ($attempts >= $this->maxAttempts) {
return response()->json([
'message' => 'Too many requests. Please try again later.'
], Response::HTTP_TOO_MANY_REQUESTS);
}
Cache::put($key, $attempts + 1, $this->decaySeconds);
$response = $next($request);
$response->headers->set('X-RateLimit-Limit', $this->maxAttempts);
$response->headers->set('X-RateLimit-Remaining', $this->maxAttempts - ($attempts + 1));
return $response;
}
}
What’s Happening Here?
- We store each IP’s request count in the cache (Redis, file, or database depending on your config).
- If the request count exceeds the limit (
maxAttempts), we block further requests.
- Otherwise, we increment the count and allow the request.
- We also send back rate-limit headers (
X-RateLimit-Limit and X-RateLimit-Remaining) so clients know their current quota.
Step 3: Register the Middleware
Open app/Http/Kernel.php and add your middleware under the $routeMiddleware array:
protected $routeMiddleware = [
// ...
'custom.throttle' => \App\Http\Middleware\CustomRateLimiter::class,
];
Step 4: Apply Middleware to Routes
Now, you can use it in your API routes.
Route::middleware('custom.throttle')->group(function () {
Route::get('/data', [DataController::class, 'index']);
});
Or apply it to a single route:
Route::get('/user', [UserController::class, 'show'])->middleware('custom.throttle');
Bonus: Different Limits for Different Users
You can extend the logic to give premium users higher limits:
$key = sprintf('rate_limit:%s', $request->user()?->id ?: $request->ip());
$limit = $request->user()?->isPremium() ? 100 : 20;
Then replace $this->maxAttempts with $limit in your logic.
Best Practices for API Rate-Limiting
1. Store rate-limit data in Redis for speed and reliability.
2. Return rate-limit headers so clients can self-throttle.
3. Combine with authentication for user-specific control.
4. Monitor blocked requests using logging or metrics.
Why This Matters
Rate-limiting is not just a security feature — it’s also about stability and fair resource allocation.
By controlling API usage, you ensure all clients get consistent performance and protect your infrastructure from overload.
✅ Conclusion
Building your own rate-limiting middleware in Laravel is a great exercise to understand how requests flow through your application.
Even though Laravel provides a powerful built-in system, having custom control lets you tailor performance and protection to your specific project needs.
Start small — protect a few routes, test, and iterate as your API grows.
With this setup, you’re one step closer to building a robust, production-ready Laravel API.