Installation:
composer require webrek/laravel-idempotency
php artisan vendor:publish --provider="Webrek\Idempotency\IdempotencyServiceProvider"
This publishes the config file (config/idempotency.php) and migration (database/migrations/xxxx_create_idempotency_keys_table.php).
Run Migration:
php artisan migrate
Creates the idempotency_keys table to store keys and responses.
First Use Case:
Apply the middleware to a route handling state-changing requests (e.g., POST /orders):
Route::post('/orders', [OrderController::class, 'store'])
->middleware('idempotency');
Clients must include the Idempotency-Key header (e.g., UUID) in their request:
POST /orders HTTP/1.1
Idempotency-Key: abc123...
Test the Flow:
idempotency_keys table.Middleware Integration:
idempotency middleware to routes where idempotency is required (e.g., POST, PUT, PATCH).GET endpoints).Key Generation:
const key = crypto.randomUUID();
fetch('/orders', {
method: 'POST',
headers: { 'Idempotency-Key': key },
body: JSON.stringify({ product_id: 123 })
});
Response Handling:
response() helper:
return response()->idempotent($request, $response);
Cleanup:
idempotency.ttl (default: 1 day) via Laravel’s scheduler.\Webrek\Idempotency\Facades\Idempotency::delete($key);
Conditional Idempotency: Use middleware groups or route conditions to apply idempotency selectively:
Route::post('/orders/{id}', [OrderController::class, 'update'])
->middleware(['idempotency', 'throttle:60']);
Custom Storage:
Extend the IdempotencyRepository interface to use Redis or another driver:
// config/idempotency.php
'driver' => 'redis',
Webhook Handling: For webhooks (e.g., Stripe), use idempotency to avoid duplicate processing:
Route::post('/webhooks/stripe', [WebhookController::class, 'handle'])
->middleware('idempotency');
Testing: Mock the idempotency middleware in tests:
$this->withHeaders(['Idempotency-Key' => 'test-key'])
->post('/orders', [...])
->assertSuccessful();
Key Collisions:
Large Responses:
idempotency_keys table.config('idempotency.store_body') = false to store only metadata (status code, headers).Race Conditions:
TTL Misconfiguration:
idempotency.ttl too low (e.g., 5 minutes) may cause legitimate retries to fail.Middleware Order:
idempotency before authentication/authorization middleware to avoid processing the request twice.Route::post('/orders', [...])
->middleware(['idempotency', 'auth:sanctum']);
Check the Database:
SELECT * FROM idempotency_keys WHERE key = 'abc123...';
Log Middleware Events:
config/idempotency.php:
'log' => env('IDEMPOTENCY_LOG', true),
Idempotency entries.Test Edge Cases:
curl -X POST -H "Idempotency-Key: abc123..." http://your-api/orders
Custom Key Validation: Override the default key validation (e.g., enforce regex patterns):
// app/Providers/IdempotencyServiceProvider.php
public function boot()
{
Idempotency::validateKeyUsing(function ($key) {
return preg_match('/^[a-f0-9]{32}$/', $key);
});
}
Event Listeners:
Listen for idempotency events (e.g., IdempotencyStored, IdempotencyRetrieved):
// app/Providers/EventServiceProvider.php
protected $listen = [
\Webrek\Idempotency\Events\IdempotencyStored::class => [
\App\Listeners\LogIdempotency::class,
],
];
API Versioning: Scope keys by API version to avoid conflicts across versions:
// In middleware
$key = "v2_{$request->header('Idempotency-Key')}";
Rate Limiting:
Combine with Laravel’s throttle middleware to limit retries:
Route::post('/orders', [...])
->middleware(['idempotency', 'throttle:10,1']);
How can I help you explore Laravel packages today?