spatie/laravel-rate-limited-job-middleware
Laravel job middleware to rate limit queued jobs. Allow a configurable number of jobs per second and automatically release throttled jobs for a delay. Easy to attach per job via middleware, with support for time-based attempts when rate limiting.
Installation:
composer require spatie/laravel-rate-limited-job-middleware
Publish the config file (optional):
php artisan vendor:publish --provider="Spatie\RateLimitedJobMiddleware\RateLimitedJobMiddlewareServiceProvider"
Register Middleware:
Apply the middleware to a job class in its handle() method or globally via app/Console/Kernel.php:
protected $middleware = [
\Spatie\RateLimitedJobMiddleware\RateLimitedJobMiddleware::class,
];
First Use Case:
Configure rate limits in config/rate-limited-job-middleware.php:
'limits' => [
'send-notification' => [
'maxJobs' => 5,
'perMinute' => true,
'key' => 'user_id',
],
],
Then dispatch a job:
SendNotification::dispatch($user)->onQueue('emails');
Per-Job Rate Limiting:
Define limits in config/rate-limited-job-middleware.php with:
maxJobs: Maximum allowed jobs.perMinute/perHour/perDay: Time window.key: Dynamic key (e.g., user_id, tenant_id) to scope limits.Example:
'limits' => [
'process-invoice' => [
'maxJobs' => 3,
'perHour' => true,
'key' => 'invoice_id',
],
],
Global vs. Per-Job Middleware:
Kernel.php (use cautiously).class ProcessInvoice implements ShouldQueue {
use Dispatchable, InteractsWithQueue;
public function handle() {
// Job logic
}
public function middleware() {
return [new \Spatie\RateLimitedJobMiddleware\RateLimitedJobMiddleware()];
}
}
Dynamic Keys: Use closures or job payloads to dynamically generate keys:
'limits' => [
'sync-data' => [
'maxJobs' => 10,
'perMinute' => true,
'key' => function ($job) {
return "user_{$job->userId}_source_{$job->source}";
},
],
],
Queue Integration:
cache) is shared (e.g., Redis).Fallback Behavior:
RateLimitedJobMiddleware to log, retry, or throw exceptions:
class CustomRateLimitedMiddleware extends RateLimitedJobMiddleware {
public function handle($job, $next) {
try {
parent::handle($job, $next);
} catch (RateLimitExceededException $e) {
Log::warning("Rate limit exceeded for {$job->job}", ['job' => $job]);
throw $e; // Or handle differently
}
}
}
Cache Driver Mismatch:
cache driver. If using a non-shared cache (e.g., local file cache), rate limits won’t sync across workers.'driver' => env('RATE_LIMIT_CACHE_DRIVER', 'redis'),
Key Collisions:
user_id, tenant_id + action).Time Window Misconfiguration:
perMinute/perHour settings may cause unexpected throttling.spatie/laravel-rate-limiting package (dependency) for detailed logs.Middleware Order:
RateLimitedJobMiddleware before other middleware that might modify the job (e.g., Retryable).public function middleware() {
return [
new \Spatie\RateLimitedJobMiddleware\RateLimitedJobMiddleware(),
new \Illuminate\Queue\Middleware\Retryable(),
];
}
Testing Quirks:
public function tearDown(): void {
Artisan::call('cache:clear');
parent::tearDown();
}
Log Rate Limit Checks:
Enable debug mode in config/rate-limited-job-middleware.php:
'debug' => env('RATE_LIMIT_DEBUG', false),
Logs will appear in storage/logs/laravel.log.
Inspect Stored Limits: Dump the cache to verify stored limits:
\Illuminate\Support\Facades\Cache::get('rate_limits');
Override Storage: Extend the package to use a custom storage mechanism (e.g., database):
use Spatie\RateLimitedJobMiddleware\RateLimit;
class CustomRateLimit extends RateLimit {
public function __construct() {
$this->storage = new DatabaseRateLimitStorage();
}
}
Custom Rate Limit Logic:
Extend the RateLimit class to implement dynamic limits:
class DynamicRateLimit extends \Spatie\RateLimitedJobMiddleware\RateLimit {
public function allowed($job, $key) {
if ($job->isHighPriority()) {
return true; // Bypass limits for high-priority jobs
}
return parent::allowed($job, $key);
}
}
Event-Based Notifications: Listen for rate limit events to trigger alerts:
RateLimitExceeded::listen(function ($job, $key) {
Notification::route('mail', 'admin@example.com')
->notify(new RateLimitExceededNotification($job, $key));
});
Bulk Job Handling: For batch jobs, use a single key to group multiple dispatches:
'limits' => [
'batch-process' => [
'maxJobs' => 20,
'perMinute' => true,
'key' => 'batch_id_123', // Static key for grouped jobs
],
],
How can I help you explore Laravel packages today?