Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Openapi Validator Laravel Package

kirschbaum-development/laravel-openapi-validator

Validate Laravel requests and responses against your OpenAPI (Swagger) specification. Catch schema mismatches early with middleware, clear error messages, and minimal setup, keeping your API contract and documentation in sync.

View on GitHub
Deep Wiki
Context7

Getting Started

Pin the version explicitly rather than floating the major — 2.0.2 is a maintenance bump that widens the symfony/psr-http-message-bridge constraint to ^8, and you want that resolution to be a deliberate decision, not a surprise in CI.

composer require kirschbaum-development/laravel-openapi-validator:2.0.2
php artisan vendor:publish --tag=openapi-validator-config   # verify tag name in README

Where to look first, in this order:

  1. config/openapi-validator.php — the spec path and the enable/disable switches. Everything you'll tune lives here.
  2. The middleware class itself (src/Middleware/...). It's a thin adapter: Laravel request → PSR-7 → validator → Laravel validation error. Read it once end-to-end; it's small, and knowing exactly where the exception is thrown is what makes shadow mode and debugging tractable.
  3. The test trait. This is the recommended entry point and where you'll spend most of your time.

First use case — do not start with middleware. Take one existing endpoint you already have a feature test for, point the config at your spec, and have the package validate requests inside that test. Blast radius is zero: a red test means the spec or the test fixture is wrong, and nothing in production can break.

use KirschbaumDevelopment\OpenApiValidator\Tests\ValidatesOpenApi; // confirm exact namespace

class UserUpdateTest extends TestCase
{
    use ValidatesOpenApi;

    public function test_it_updates_a_user(): void
    {
        $this->putJson("/api/users/{$user->id}", ['name' => 'Ada'])
            ->assertOk();
    }
}

Get one green. Then add the second endpoint, which is where you'll hit your first nullable/enum disagreement between the spec and reality. That friction is the whole point of the first day — you want to learn it against one test, not against a route group.

Implementation Patterns

Test-suite validation as the primary mode

This is the highest-leverage, lowest-risk pattern and should be your default. Attach the trait to a base test case or to a dedicated contract test class, and let every $this->getJson(...) / postJson(...) in that class be validated automatically.

// tests/Feature/Contract/UsersApiTest.php
class UsersApiTest extends TestCase
{
    use ValidatesOpenApi;

    /** @dataProvider endpoints */
    public function test_endpoint_satisfies_contract(string $method, string $uri, array $payload): void
    {
        $this->json($method, $uri, $payload)->assertSuccessful();
    }
}

Two things make this scale:

  • Separate the contract group from the behavioral group. Run contract tests as their own PHPUnit/Pest group in CI (--group=openapi) so a spec failure produces a distinct, unmistakeable signal instead of blending into a wall of red.
  • Feed them from factories that are spec-valid by construction. The biggest ongoing tax with this package is that your test fixtures were written to satisfy your controllers, not your spec. When the trait rejects factory output, the fix is nearly always the factory (a missing field, a null where the spec says required) — not the spec. Budget for this: it's a half-day of factory cleanup when you first attach the trait to a mature suite.

Route-group middleware, shadow mode first

Once the test suite is green on a bounded set of endpoints, add the middleware — but in shadow mode. Register it on one route group, let it validate, catch the exception, log it, and continue the request untouched.

Route::middleware(['api', ValidateOpenApi::class])->group(function () {
    Route::apiResource('users', UserController::class);
});

For shadow mode, subclass the middleware and wrap the validation in a try/catch that reports and returns $next($request). Two to four weeks of shadow logs on a real route group tells you more about your spec's fidelity than any amount of test writing, because it exercises the payload shapes your actual clients send — including the ones nobody wrote a test for.

Response validation is a separate decision

The package supports optional response validation. Treat it as a distinct rollout, not a checkbox:

  • Turn it on first in the test suite only. Your error envelopes, 204s, pagination wrappers, and empty-collection shapes all now need schemas in the spec.
  • The classic failure: you enable response validation, and your 422/500 handlers start failing validation because your error envelope ({message, errors}) isn't documented in the spec. Document your error shape once as a reusable component and $ref it from every 4xx/5xx response.
  • Leave it off for production middleware until you've measured the per-request cost on your actual spec. Validation work scales with $ref depth and oneOf/anyOf branching.

Spec organization

  • Keep the spec in the repo at a path the config can resolve deterministically (base_path('api/openapi.yaml'), not a relative path that depends on cwd).
  • Multi-file specs with external $refs are supported by the underlying validator but make the resolution path brittle — verify it works locally and in CI, where the directory layout may differ.
  • Add a spec linter (Spectral or similar) as a separate CI step. Validating your spec with a linter before the middleware sees it saves you from a whole class of confusing runtime errors.

Integration tips

  • Middleware ordering matters. It must run before the controller and before anything that consumes the request body. If you also have FormRequest validation, you'll get two validation layers — during the pilot, keep FormRequest rules in place and treat the OpenAPI layer as an additive contract check; don't delete rules until the middleware is enforcing in production.
  • CI gating: make the contract group a required check and add a PR checklist item — "spec updated?" — paired with it. The middleware will get switched off within a quarter if spec changes aren't reviewed like code.
  • Error envelope: decide up front what a violation returns. The package returns Laravel-shaped validation errors, which is good for consistency with the rest of your app; if you have a house envelope, wrap it in a subclassed middleware rather than fighting the default in config.

Gotchas and Tips

Nearly every false positive is a spec defect, not a code defect. Internalize this before you start triaging. When the middleware rejects a request that your application happily accepts, your first move is to open the spec, not the controller. This expectation-setting is the single biggest predictor of whether the rollout survives contact with the team.

nullable is the #1 source of pain. OpenAPI 3.0 expresses it as nullable: true alongside type; JSON Schema / OpenAPI 3.1 uses type: [string, "null"]. Mixing the dialects produces a validator that rejects null even though your app sends it — or accepts it when you didn't mean to. Confirm which OpenAPI version the underlying validator is honouring and be consistent across the whole spec.

Strictness you'll trip over:

  • format assertions (uuid, date-time, email) fire on values your app tolerates — e.g. a date-time without a timezone offset.
  • Enums are strictly typed; 1 and "1" are different. Integer-backed enums in your database cast to strings in JSON unless you're careful.
  • additionalProperties: false rejects extra keys. If you have "just one more field for now" habits, this will surface them all at once.
  • Missing required on path parameters produces confusing 500s rather than clean 422s — the validator can't match the route it expects.

Route coverage gaps. Middleware only runs on requests that reach your routes. 404s from unmatched paths, requests rejected at an earlier middleware, and anything served outside the group are never validated — so a green test suite does not mean "every request is validated." Be explicit about which route groups are in scope.

Fail-open vs fail-closed when the spec can't be loaded. If the spec file is missing, unreadable, or unparseable, decide deliberately per environment: fail loudly in tests and CI (you want that to be an error), and fail open with alerting in production during the pilot. An unhandled exception from a bad spec path in production is a total outage from a config typo — a very avoidable one.

Performance. The spec is parsed and resolved per request unless the package caches it. On a large multi-file spec with deep $ref graphs, this is measurable. Benchmark before enabling enforcement broadly; scope it to route groups and don't assume it's free.

Debugging workflow when a request unexpectedly fails:

  1. Dump the actual validation error object — it names the parameter and the constraint.
  2. Reproduce as a single feature test with the trait, so you get a fast loop.
  3. Check the spec fragment for that operation, including the referenced component schemas, and compare against the literal request payload.
  4. If the spec looks right, check for $ref resolution issues (relative paths, files not committed).

Extension points worth knowing: the middleware is small enough to subclass, which is how you should implement shadow mode, custom logging, and your house error envelope — rather than forking. Config-driven per-route toggling lets you exclude single endpoints during triage without disabling the group.

Dependency posture. 58 stars, zero recorded dependents, single-vendor maintenance. It's MIT-licensed and thin, so vendor-or-fork is a realistic exit plan — but treat it as a narrow, replaceable adapter, not load-bearing infrastructure. Pin 2.0.2, and add a CI check (composer why symfony/psr-http-message-bridge or a lockfile diff guard) so a routine composer update can't silently pull bridge v8 and lift your PHP floor out from under a legacy service.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect