dive-be/laravel-dry-requests
Laravel Dry Requests lets your frontend validate input against the same Laravel FormRequest rules without running controller logic. Send a dry-run request, get validation results, and keep frontend and backend validation in sync with zero duplicated rules.
composer require dive-be/laravel-dry-requests
Laravel's package auto-discovery registers the service provider; if you need to reason about it, check vendor/dive-be/laravel-dry-requests/src for the middleware class and any config stub before wiring it in.
Register the package middleware so it can intercept requests early:
// app/Http/Kernel.php (Laravel ≤10)
protected $middleware = [
\Dive\DryRequests\Http\Middleware\HandleDryRequests::class,
];
Then hit any endpoint that is backed by a FormRequest with the dry-run header:
curl -X POST https://app.test/api/posts \
-H "X-Dry-Run: true" \
-H "Accept: application/json" \
-d '{"title": ""}'
204 No Content → validation would have passed, and the controller was not executed.422 Unprocessable Entity → the normal validation error payload is returned.403 → authorize() failed (it still runs).The first real use case is a frontend that wants to tell a user "this form is valid" without persisting anything — the middleware short-circuits after validation instead of invoking the controller.
SPA/mobile pre-flight validation. Add the middleware to the API route group your frontend already calls, then have Axios/fetch send X-Dry-Run: true on blur or before submit. You reuse the exact rules() and authorize() logic from the real endpoint, so the client never drifts from the server's rules.
Feature-test helper. Wrap it in a test trait so validation can be asserted independently of controller behaviour:
public function assertValidationPasses(string $uri, array $data): void
{
$this->withHeader('X-Dry-Run', 'true')
->postJson($uri, $data)
->assertNoContent();
}
Multi-step wizards. Validate step 2 against a payload the user has not yet submitted by dry-running the final "store" endpoint. Because nothing is written and no events/jobs fire, this is safe to call repeatedly.
Conditional middleware. Guard against production exposure — either scope the middleware to specific route groups or register it conditionally in a service provider:
if (! app()->isProduction()) {
$kernel->pushMiddleware(HandleDryRequests::class);
}
Pair with stopOnFirstFailure() / after() hooks. Whatever validation behaviour the FormRequest defines is what the dry run exercises, so keep the rules and hooks on the request class rather than duplicated in controllers.
"minimum-stability"/ignore-platform-req gymnastics or a small fork updating the service provider. Budget for that before adopting.FormRequest validation is intercepted. Inline $request->validate([...]) inside a controller never runs during a dry request — you'll get a false 204. Move rules into a FormRequest if you want dry-run coverage.SubstituteBindings (and after auth middleware) so route-model binding and authorize() see the same route parameters they would in a real request. Conversely, anything before it — throttling, tenancy resolution, session start — still executes, so counters and side effects are not fully avoided.authorize() runs. A dry run is not a bypass; users without permission get 403, and authorization policies run against bound models./posts/{post} will resolve the model. That is usually fine, but it means a dry run is not free.VerifyCsrfToken (only if you accept the tradeoff).php artisan route:list -v shows which middleware is attached to a route; a quick dd() inside the middleware's handle() confirms whether it is being reached before the controller.204 as "valid" and parse 422 with the standard Laravel error envelope (errors), so existing form-error components work unchanged.How can I help you explore Laravel packages today?