1. Introduction: Why Laravel API Rate Limiting Matters
In today’s API-driven world, protecting your endpoints from abuse is not just good practice—it’s essential. Rate limiting is a crucial defense mechanism for your Laravel applications, preventing malicious attacks, reducing server load, and ensuring fair resource allocation among users.
Without proper rate limiting, your API is vulnerable to:
- Denial of Service (DoS) attacks
- Brute force attempts on authentication endpoints
- Excessive resource consumption by greedy clients
- Increased infrastructure costs due to unnecessary traffic
- Degraded performance for legitimate users
This comprehensive guide will walk you through implementing robust rate-limiting strategies in Laravel, from basic IP-based throttling to advanced custom solutions using the Laravel Throttle package. By the end, you’ll have the knowledge to deploy a secure, optimized API that can handle real-world traffic patterns while protecting against common threats.
Table of Contents
2. What Is API Rate Limiting?
Rate limiting is a strategy for controlling the amount of incoming and outgoing traffic to or from a network, API, or service. In the context of web applications, it restricts the number of requests a client can make to your API within a specific time frame.
For example, you might configure your API to allow:
- 60 requests per minute per IP address
- 1000 requests per day per API key
- 5 login attempts per hour for a single user
When a client exceeds these limits, the server typically responds with a 429 “Too Many Requests” HTTP status code, often including headers that indicate when the client can resume making requests.
Rate limiting serves multiple purposes:
- Security: Prevents brute force attacks and DDoS attempts
- Resource management: Ensures fair distribution of server resources
- Cost control: Limits API usage based on subscription tiers
- Performance optimization: Prevents server overload during traffic spikes
3. Common Rate Limiting Strategies
IP Address
The most basic form of rate limiting tracks requests by the client’s IP address. This approach is simple to implement but has limitations:
Pros:
- Easy to implement
- Requires no client configuration
- Works for all types of requests
Cons:
- Multiple users behind a NAT or proxy may share the same IP
- IP addresses can be spoofed
- Mobile users may frequently change IPs
API Key
API keys provide a more reliable way to identify and throttle clients:
Pros:
- More precise client identification
- Can be revoked individually
- Allows for different rate limits per client
Cons:
- Requires key management infrastructure
- Keys can be stolen if not properly secured
- Additional overhead for authentication
Client ID
Using unique client identifiers can provide fine-grained control:
Pros:
- Can be tied directly to user accounts
- Enables tiered access levels
- More difficult to circumvent than IP-based limits
Cons:
- Requires user registration/authentication
- More complex to implement
- Increased database load for tracking
4. Understanding Laravel Middleware
Before diving into rate-limiting implementation, it’s important to understand Laravel’s middleware architecture. Middleware acts as a filtering mechanism for HTTP requests entering your application.
Laravel’s middleware provides a convenient layer where rate limiting logic can be applied:
namespace App\Http\Middleware;
use Closure;
class RateLimitMiddleware
{
public function handle($request, Closure $next)
{
// Rate limiting logic goes here
return $next($request);
}
}
Laravel comes with built-in rate limiting middleware, but understanding how middleware works will help you implement custom solutions when needed.
Key middleware concepts for rate limiting:
- Middleware can be applied globally, to route groups, or individual routes
- Order matters when applying multiple middleware
- Middleware can terminate early, preventing request processing
- Response headers can be modified to provide rate limit information
5. Setting Up the Laravel API Project
Prerequisites
Before starting, ensure you have:
- PHP 8.0 or higher
- Composer
- Git
- A database system (MySQL, PostgreSQL, etc.)
- Basic knowledge of Laravel
Cloning the Project
Let’s start by cloning a basic Laravel API project:
git clone https://github.com/yourusername/laravel-api-rate-limiting.git
cd laravel-api-rate-limiting
If you don’t have an existing project, create a new Laravel project:
composer create-project laravel/laravel laravel-api-rate-limiting
cd laravel-api-rate-limiting
Installing Dependencies & Serving the App
Install the required dependencies:
composer install
cp .env.example .env
php artisan key:generate
Configure your database connection in the .env
file:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_api
DB_USERNAME=root
DB_PASSWORD=
Run migrations to set up your database:
php artisan migrate
Start the development server:
php artisan serve
6. Remote Database Configuration in Hostinger
If you’re using Hostinger as your hosting provider, configuring your database is straightforward:
- Log in to your Hostinger dashboard
- Navigate to Databases and click “Add Database”
- Select MySQL as the database type
- Choose your preferred region
- Name your database (e.g.,
laravel_api
) - Create a new user or select an existing one
- Note the connection details provided
Update your .env
file with the Hostinger database credentials:
DB_CONNECTION=mysql
DB_HOST=your-Hostinger-db-host
DB_PORT=3306
DB_DATABASE=laravel_api
DB_USERNAME=your-username
DB_PASSWORD=your-password
To ensure secure connections, Hostinger requires SSL for database connections. Add the following to your config/database.php
file:
'mysql' => [
// other configuration...
'sslmode' => env('DB_SSLMODE', 'require'),
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false,
]) : [],
],
7. Exploring Available API Routes
Let’s create some example API routes that we’ll protect with rate limiting:
// routes/api.php
use App\Http\Controllers\API\AuthController;
use App\Http\Controllers\API\ProductController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
// Public routes
Route::post('/login', [AuthController::class, 'login']);
Route::post('/register', [AuthController::class, 'register']);
Route::get('/products', [ProductController::class, 'index']);
// Protected routes
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
Route::apiResource('/products/manage', ProductController::class)->except(['index']);
});
Create the necessary controllers:
php artisan make:controller API/AuthController
php artisan make:controller API/ProductController --resource
Implement basic controller logic for testing:
// App/Http/Controllers/API/AuthController.php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
public function register(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed',
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
$token = $user->createToken('auth_token')->plainTextToken;
return response()->json([
'user' => $user,
'access_token' => $token,
'token_type' => 'Bearer',
]);
}
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (!Auth::attempt($request->only('email', 'password'))) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
$user = User::where('email', $request->email)->firstOrFail();
$token = $user->createToken('auth_token')->plainTextToken;
return response()->json([
'user' => $user,
'access_token' => $token,
'token_type' => 'Bearer',
]);
}
}
8. Installing Laravel Throttle Package
While Laravel includes built-in rate limiting capabilities, we’ll enhance our implementation with the laravel throttle
package for more advanced features:
composer require graham-campbell/throttle
Publish the configuration file:
php artisan vendor:publish --provider="GrahamCampbell\Throttle\ThrottleServiceProvider"
This creates a config/throttle.php
file that you can customize:
<?php
return [
/*
|--------------------------------------------------------------------------
| Cache Driver
|--------------------------------------------------------------------------
|
| This defines the cache driver to be used. It may be the name of any
| driver set in config/cache.php. Setting it to null will use the driver
| you have set as default in config/cache.php.
|
| Default: null
|
*/
'driver' => null,
];
9. Rate Limiting Techniques in Laravel
9.1 Blocking Specific IP Addresses
Before implementing rate limiting, you might want to completely block certain IP addresses known for abuse:
Create a middleware to block specific IPs:
php artisan make:middleware BlockIpAddresses
Implement the middleware:
// app/Http/Middleware/BlockIpAddresses.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class BlockIpAddresses
{
public $blockedIps = [
'192.168.1.1',
'10.0.0.5',
// Add more IPs as needed
];
public function handle(Request $request, Closure $next)
{
if (in_array($request->ip(), $this->blockedIps)) {
abort(403, 'You are restricted to access the site.');
}
return $next($request);
}
}
Register the middleware in app/Http/Kernel.php
:
protected $routeMiddleware = [
// Other middleware...
'blockip' => \App\Http\Middleware\BlockIpAddresses::class,
];
Apply it to routes:
// routes/api.php
Route::middleware(['blockip'])->group(function () {
// Your routes here
});
9.2 Throttling by IP Address
Laravel provides a built-in throttle
middleware for IP-based rate limiting:
// routes/api.php
// Allow 60 requests per minute per IP
Route::middleware('throttle:60,1')->group(function () {
Route::get('/products', [ProductController::class, 'index']);
});
For more granular control, create a custom middleware:
php artisan make:middleware IpThrottleMiddleware
Implement IP-based throttling:
// app/Http/Middleware/IpThrottleMiddleware.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Cache\RateLimiter;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class IpThrottleMiddleware
{
protected $limiter;
public function __construct(RateLimiter $limiter)
{
$this->limiter = $limiter;
}
public function handle(Request $request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
{
$key = 'ip:' . $request->ip();
if ($this->limiter->tooManyAttempts($key, $maxAttempts)) {
$seconds = $this->limiter->availableIn($key);
return response()->json([
'message' => 'Too many requests. Please try again later.',
'retry_after' => $seconds,
], Response::HTTP_TOO_MANY_REQUESTS)
->header('Retry-After', $seconds);
}
$this->limiter->hit($key, $decayMinutes * 60);
$response = $next($request);
// Add rate limit headers to the response
$response->headers->add([
'X-RateLimit-Limit' => $maxAttempts,
'X-RateLimit-Remaining' => $maxAttempts - $this->limiter->attempts($key) + 1,
]);
return $response;
}
}
Register and use the middleware:
// app/Http/Kernel.php
protected $routeMiddleware = [
// Other middleware...
'ip.throttle' => \App\Http\Middleware\IpThrottleMiddleware::class,
];
// routes/api.php
Route::middleware('ip.throttle:30,1')->group(function () {
Route::get('/products', [ProductController::class, 'index']);
});
9.3 Throttling by User ID and Session
For authenticated routes, throttling by user ID provides more accurate control:
// routes/api.php
Route::middleware(['auth:sanctum', 'throttle:60,1'])->group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
});
Create a custom middleware for user-based throttling:
php artisan make:middleware UserThrottleMiddleware
Implement the middleware:
// app/Http/Middleware/UserThrottleMiddleware.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Cache\RateLimiter;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class UserThrottleMiddleware
{
protected $limiter;
public function __construct(RateLimiter $limiter)
{
$this->limiter = $limiter;
}
public function handle(Request $request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
{
// Use user ID if authenticated, otherwise use IP
$key = $request->user()
? 'user:' . $request->user()->id
: 'ip:' . $request->ip();
if ($this->limiter->tooManyAttempts($key, $maxAttempts)) {
$seconds = $this->limiter->availableIn($key);
return response()->json([
'message' => 'Too many requests. Please try again later.',
'retry_after' => $seconds,
], Response::HTTP_TOO_MANY_REQUESTS)
->header('Retry-After', $seconds);
}
$this->limiter->hit($key, $decayMinutes * 60);
$response = $next($request);
$response->headers->add([
'X-RateLimit-Limit' => $maxAttempts,
'X-RateLimit-Remaining' => $maxAttempts - $this->limiter->attempts($key) + 1,
]);
return $response;
}
}
Register and use the middleware:
// app/Http/Kernel.php
protected $routeMiddleware = [
// Other middleware...
'user.throttle' => \App\Http\Middleware\UserThrottleMiddleware::class,
];
// routes/api.php
Route::middleware('user.throttle:120,1')->group(function () {
Route::post('/login', [AuthController::class, 'login']);
Route::post('/register', [AuthController::class, 'register']);
});
9.4 Throttling Using Custom Headers (e.g., API Keys)
For API-key based throttling, create a middleware that looks for an API key in the request headers:
php artisan make:middleware ApiKeyThrottleMiddleware
Implement the middleware:
// app/Http/Middleware/ApiKeyThrottleMiddleware.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Cache\RateLimiter;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class ApiKeyThrottleMiddleware
{
protected $limiter;
public function __construct(RateLimiter $limiter)
{
$this->limiter = $limiter;
}
public function handle(Request $request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
{
// Get API key from header or fallback to IP
$apiKey = $request->header('X-API-KEY');
$key = $apiKey ? 'api:' . $apiKey : 'ip:' . $request->ip();
if ($this->limiter->tooManyAttempts($key, $maxAttempts)) {
$seconds = $this->limiter->availableIn($key);
return response()->json([
'message' => 'API rate limit exceeded. Please try again later.',
'retry_after' => $seconds,
], Response::HTTP_TOO_MANY_REQUESTS)
->header('Retry-After', $seconds);
}
$this->limiter->hit($key, $decayMinutes * 60);
$response = $next($request);
$response->headers->add([
'X-RateLimit-Limit' => $maxAttempts,
'X-RateLimit-Remaining' => $maxAttempts - $this->limiter->attempts($key) + 1,
]);
return $response;
}
}
Register and use the middleware:
// app/Http/Kernel.php
protected $routeMiddleware = [
// Other middleware...
'apikey.throttle' => \App\Http\Middleware\ApiKeyThrottleMiddleware::class,
];
// routes/api.php
Route::middleware('apikey.throttle:100,1')->group(function () {
Route::get('/products', [ProductController::class, 'index']);
});
9.5 Tiered Rate Limits for Guests vs. Authenticated Users
Different user types often deserve different rate limits. Let’s implement a tiered approach:
php artisan make:middleware TieredRateLimitMiddleware
Implement the middleware:
// app/Http/Middleware/TieredRateLimitMiddleware.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Cache\RateLimiter;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class TieredRateLimitMiddleware
{
protected $limiter;
public function __construct(RateLimiter $limiter)
{
$this->limiter = $limiter;
}
public function handle(Request $request, Closure $next)
{
// Define different limits based on authentication status
if ($request->user()) {
// Authenticated user - higher limits
$maxAttempts = 120;
$decayMinutes = 1;
$key = 'user:' . $request->user()->id;
// Premium users can get even higher limits
if ($request->user()->isPremium()) {
$maxAttempts = 300;
}
} else {
// Guest user - lower limits
$maxAttempts = 30;
$decayMinutes = 1;
$key = 'ip:' . $request->ip();
}
if ($this->limiter->tooManyAttempts($key, $maxAttempts)) {
$seconds = $this->limiter->availableIn($key);
return response()->json([
'message' => 'Rate limit exceeded. Please try again later.',
'retry_after' => $seconds,
], Response::HTTP_TOO_MANY_REQUESTS)
->header('Retry-After', $seconds);
}
$this->limiter->hit($key, $decayMinutes * 60);
$response = $next($request);
$response->headers->add([
'X-RateLimit-Limit' => $maxAttempts,
'X-RateLimit-Remaining' => $maxAttempts - $this->limiter->attempts($key) + 1,
]);
return $response;
}
}
Add the isPremium
method to the User model:
// app/Models/User.php
public function isPremium()
{
// Logic to determine if user has premium status
return $this->subscription_type === 'premium';
}
Register and use the middleware:
// app/Http/Kernel.php
protected $routeMiddleware = [
// Other middleware...
'tiered.throttle' => \App\Http\Middleware\TieredRateLimitMiddleware::class,
];
// routes/api.php
Route::middleware('tiered.throttle')->group(function () {
Route::get('/products', [ProductController::class, 'index']);
// Other routes that should have tiered rate limiting
});
10. Advanced Throttling with Laravel Throttle Package
The Laravel Throttle package provides more advanced features for rate limiting. Let’s explore how to use its key methods:
First, inject the throttle manager into your controller:
// app/Http/Controllers/API/AdvancedThrottleController.php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use GrahamCampbell\Throttle\Facades\Throttle;
use Illuminate\Http\Request;
class AdvancedThrottleController extends Controller
{
public function throttledEndpoint(Request $request)
{
// Implementation will go here
}
}
attempt
The attempt
method checks if an action can be performed and increments the counter if allowed:
public function throttledEndpoint(Request $request)
{
$key = $request->user() ? $request->user()->id : $request->ip();
// Allow 5 attempts per minute
$limit = 5;
$time = 60;
if (Throttle::attempt(['key' => $key, 'limit' => $limit, 'time' => $time])) {
// Action is allowed
return response()->json(['message' => 'Request processed successfully']);
} else {
// Too many attempts
return response()->json(['message' => 'Too many attempts, please try again later'], 429);
}
}
hit
The hit
method increments the counter without checking the limit:
public function incrementCounter(Request $request)
{
$key = $request->ip();
// Increment the counter with 60 seconds expiry
Throttle::hit(['key' => $key, 'time' => 60]);
return response()->json(['message' => 'Counter incremented']);
}
clear
The clear
method resets the counter:
public function resetCounter(Request $request)
{
$key = $request->ip();
Throttle::clear(['key' => $key]);
return response()->json(['message' => 'Counter reset successfully']);
}
count
The count
method returns the current counter value:
public function getCounter(Request $request)
{
$key = $request->ip();
$count = Throttle::count(['key' => $key]);
return response()->json(['counter' => $count]);
}
check
The check
method verifies if an action is allowed without incrementing the counter:
public function checkLimit(Request $request)
{
$key = $request->ip();
$limit = 5;
$time = 60;
$allowed = Throttle::check(['key' => $key, 'limit' => $limit, 'time' => $time]);
return response()->json([
'allowed' => $allowed,
'remaining_attempts' => $allowed ? ($limit - Throttle::count(['key' => $key])) : 0
]);
}
Create a route for the advanced throttle controller:
// routes/api.php
Route::get('/advanced-throttle', [AdvancedThrottleController::class, 'throttledEndpoint']);
Route::get('/advanced-throttle/count', [AdvancedThrottleController::class, 'getCounter']);
Route::get('/advanced-throttle/check', [AdvancedThrottleController::class, 'checkLimit']);
Route::post('/advanced-throttle/reset', [AdvancedThrottleController::class, 'resetCounter']);
11. Customizing Throttle Responses
Customize how your API responds when rate limits are exceeded by creating a custom exception handler:
// app/Exceptions/Handler.php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Throwable;
class Handler extends ExceptionHandler
{
// Other methods...
public function render($request, Throwable $exception)
{
if ($exception instanceof ThrottleRequestsException) {
return response()->json([
'error' => 'Rate limit exceeded',
'message' => 'You have made too many requests. Please wait before trying again.',
'retry_after' => $exception->getHeaders()['Retry-After'],
'docs_url' => 'https://api.example.com/docs/rate-limits',
], 429);
}
return parent::render($request, $exception);
}
}
12. Logging Rate Limit Violations
Track rate limit violations to identify potential abuse patterns:
Create a custom middleware for logging:
php artisan make:middleware LogRateLimitMiddleware
Implement the middleware:
// app/Http/Middleware/LogRateLimitMiddleware.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Cache\RateLimiter;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class LogRateLimitMiddleware
{
protected $limiter;
public function __construct(RateLimiter $limiter)
{
$this->limiter = $limiter;
}
public function handle(Request $request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
{
$key = $request->user() ? 'user:' . $request->user()->id : 'ip:' . $request->ip();
// Check if this request will exceed the limit
if ($this->limiter->attempts($key) >= $maxAttempts - 1) {
// Log the violation
Log::warning('Rate limit exceeded', [
'key' => $key,
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'path' => $request->path(),
'attempts' => $this->limiter->attempts($key),
'max_attempts' => $maxAttempts,
]);
// You could also notify admins or trigger alerts here
}
return $next($request);
}
}
Register and use the middleware:
// app/Http/Kernel.php
protected $routeMiddleware = [
// Other middleware...
'log.ratelimit' => \App\Http\Middleware\LogRateLimitMiddleware::class,
];
// routes/api.php
Route::middleware(['throttle:5,1', 'log.ratelimit:5,1'])->group(function () {
Route::post('/login', [AuthController::class, 'login']);
});
13. Testing Rate Limiting Locally
To test your rate limiting implementation, create a simple test script:
php artisan make:test RateLimitingTest
Implement the test:
// tests/Feature/RateLimitingTest.php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class RateLimitingTest extends TestCase
{
use RefreshDatabase;
public function test_basic_rate_limiting()
{
// Make multiple requests to a rate-limited endpoint
for ($i = 0; $i < 5; $i++) {
$response = $this->get('/api/products');
$response->assertStatus(200);
}
// The 6th request should be rate limited
$response = $this->get('/api/products');
$response->assertStatus(429);
// Verify the response contains the correct headers
$this->assertTrue($response->headers->has('Retry-After'));
}
public function test_user_based_rate_limiting()
{
// Create and authenticate a user
$user = \App\Models\User::factory()->create();
$this->actingAs($user);
// Make requests up to the limit
for ($i = 0; $i < 60; $i++) {
$response = $this->get('/api/user');
$response->assertStatus(200);
}
// The next request should be rate limited
$response = $this->get('/api/user');
$response->assertStatus(429);
}
}
Run the tests:
php artisan test --filter=RateLimitingTest
14. Deploying to Hostinger
To deploy your Laravel API with rate limiting to Hostinger:
- Push your code to a Git repository (GitHub, GitLab, Bitbucket)
- Log in to your Hostinger dashboard
- Create a new application
- Select “Laravel” as your application type
- Connect your Git repository
- Configure deployment settings:
- PHP version: 8.1+
- Environment variables (copy from your
.env
file) - Database connection details
- Add the following to your
Procfile
:
web: vendor/bin/heroku-php-apache2 public/
- Configure environment variables in the Hostinger dashboard:
APP_ENV=production
APP_DEBUG=false
APP_KEY=your-app-key
DB_CONNECTION=mysql
DB_HOST=your-Hostinger-db-host
DB_PORT=3306
DB_DATABASE=your-db-name
DB_USERNAME=your-username
DB_PASSWORD=your-password
- Deploy your application
15. Post-Deployment: Handling Laravel Routes on Hostinger
After deployment, ensure your Laravel routes are properly handled:
Create a custom Nginx configuration for your Hostinger application:
# Hostinger-nginx-config.conf
location / {
try_files $uri $uri/ /index.php?$query_string;
}
Add the configuration to Hostinger through the Hostinger dashboard:
- Navigate to your site
- Go to “Tools & Settings” > “Nginx Configuration”
- Paste the above configuration
- Save changes
16. Final Testing With Postman and CURL
Testing with Postman
- Create a new Postman collection for your API
- Add requests for your endpoints
- Use the “Runner” feature to send multiple requests in succession
- Verify that rate limiting is working by checking for 429 responses
- Examine the response headers for rate limit information
Example test script for Postman:
// Test rate limit headers
pm.test("Rate limit headers are present", function() {
pm.response.to.have.header("X-RateLimit-Limit");
pm.response.to.have.header("X-RateLimit-Remaining");
});
// If rate limited, verify 429 status
if (pm.response.code === 429) {
pm.test("Rate limited response has Retry-After header", function() {
pm.response.to.have.header("Retry-After");
});
}
Testing with CURL
Test your API endpoints with CURL commands:
# Make multiple requests to test rate limiting
for i in {1..10}; do
curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://your-api.hostinger.app/api/products
echo "\n--- Request $i complete ---\n"
sleep 1
done
Check for the rate limit headers in the response:
curl -i -H "Accept: application/json" http://your-api.hostinger.app/api/products
17. Monitoring and Analytics for Rate Limiting
Understanding how your rate limits affect users is crucial. Let’s add a section on monitoring:
// app/Http/Middleware/RateLimitAnalyticsMiddleware.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class RateLimitAnalyticsMiddleware
{
public function handle(Request $request, Closure $next)
{
$response = $next($request);
// Only track API requests
if (strpos($request->path(), 'api/') === 0) {
// Store analytics data
DB::table('rate_limit_analytics')->insert([
'path' => $request->path(),
'method' => $request->method(),
'status_code' => $response->status(),
'ip' => $request->ip(),
'user_id' => $request->user() ? $request->user()->id : null,
'user_agent' => $request->userAgent(),
'rate_limited' => $response->status() === 429,
'created_at' => now(),
]);
}
return $response;
}
}
Create the migration for the analytics table:
php artisan make:migration create_rate_limit_analytics_table
Define the migration:
// database/migrations/xxxx_xx_xx_create_rate_limit_analytics_table.php
public function up()
{
Schema::create('rate_limit_analytics', function (Blueprint $table) {
$table->id();
$table->string('path');
$table->string('method', 10);
$table->integer('status_code');
$table->string('ip');
$table->unsignedBigInteger('user_id')->nullable();
$table->string('user_agent')->nullable();
$table->boolean('rate_limited');
$table->timestamp('created_at');
});
}
Register the middleware in app/Http/Kernel.php
:
protected $middleware = [
// Other middleware...
\App\Http\Middleware\RateLimitAnalyticsMiddleware::class,
];
Create a dashboard to visualize the data:
// app/Http/Controllers/RateLimitDashboardController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class RateLimitDashboardController extends Controller
{
public function index()
{
$stats = [
'total_requests' => DB::table('rate_limit_analytics')->count(),
'rate_limited_requests' => DB::table('rate_limit_analytics')->where('rate_limited', true)->count(),
'top_rate_limited_paths' => DB::table('rate_limit_analytics')
->where('rate_limited', true)
->select('path', DB::raw('count(*) as count'))
->groupBy('path')
->orderBy('count', 'desc')
->limit(5)
->get(),
'top_rate_limited_ips' => DB::table('rate_limit_analytics')
->where('rate_limited', true)
->select('ip', DB::raw('count(*) as count'))
->groupBy('ip')
->orderBy('count', 'desc')
->limit(5)
->get(),
];
return view('rate-limit-dashboard', ['stats' => $stats]);
}
}
18. Summary and Next Steps
In this comprehensive guide, we’ve explored various techniques for implementing rate limiting in Laravel APIs:
- Basic rate limiting using Laravel’s built-in middleware
- IP-based throttling for public endpoints
- User-based throttling for authenticated requests
- API key throttling for third-party integrations
- Tiered rate limiting for different user types
- Advanced throttling with the Laravel Throttle package
- Custom response handling for rate limit violations
- Logging and monitoring rate limit events
- Testing rate limiting implementation
- Deployment to Hostinger and post-deployment configuration
Next Steps
To further enhance your API’s rate limiting:
- Implement dynamic rate limits that adjust based on server load
- Add caching to reduce database load from frequent limit checks
- Set up alerts for unusual rate limit violations that might indicate attacks
- Create a rate limit status endpoint for clients to check their current limits
- Document your rate limits clearly in your API documentation
- Consider using Redis for distributed rate limiting in multi-server setups
By implementing these rate limiting strategies, you’ve taken a significant step toward building a more secure, stable, and fair API for all users. Rate limiting not only protects your infrastructure but also ensures that your services remain available and responsive even under heavy load or during attempted abuse.
Remember that rate limiting is just one aspect of API security. Continue to improve your authentication, authorization, input validation, and other security measures to build a truly robust API ecosystem.