Skip to content

[13.x] Add HTTP idempotency middleware#59304

Open
WendellAdriel wants to merge 2 commits into13.xfrom
feat/http-idempotency
Open

[13.x] Add HTTP idempotency middleware#59304
WendellAdriel wants to merge 2 commits into13.xfrom
feat/http-idempotency

Conversation

@WendellAdriel
Copy link
Copy Markdown
Member

@WendellAdriel WendellAdriel commented Mar 20, 2026

Overview

This PR adds first-class idempotency handling for write requests so applications can safely retry POST, PUT, and PATCH operations without duplicating work.

Approach

  • add a configurable middleware that scopes and stores request fingerprints and responses
  • replay matching responses, reject conflicting reuse, and guard concurrent in-flight duplicates
  • expose the middleware through the default alias map and controller attributes, with integration coverage for the main request flows

Examples

Route::post('/orders', OrderController::class)
    ->middleware(Idempotent::using(ttl: 600));
#[Idempotent]
class OrderController
{
    public function __invoke(): Response
    {
        // ...
    }
}
class OrderController
{
    #[Idempotent(scope: 'user')]
    public function store(): Response
    {
        // ...
    }
}

Introduce an `Idempotent` middleware that caches responses keyed by a
client-supplied idempotency header, preventing duplicate side-effects
when requests are retried. Supports configurable TTL, required/optional
key enforcement, user/ip/global scoping, custom header names, concurrent
request locking, JSON payload normalization, and redirect replay.

Also adds an `#[Idempotent]` controller attribute for declarative usage
and registers `idempotent` as a default middleware alias.
@WendellAdriel WendellAdriel marked this pull request as ready for review March 20, 2026 19:01
@WendellAdriel WendellAdriel changed the title Add HTTP idempotency middleware [13.x] Add HTTP idempotency middleware Mar 25, 2026
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add types here:

Suggested change
* @param \Closure $next
* @param \Closure(\Illuminate\Http\Request): \Symfony\Component\HttpFoundation\Response $next


if (is_null($clientKey) || $clientKey === '') {
if ($required) {
throw new HttpException(400, "Missing required header: {$header}");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could work with the constants here:

Suggested change
throw new HttpException(400, "Missing required header: {$header}");
throw new HttpException(Response::HTTP_BAD_REQUEST, "Missing required header: {$header}");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants