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

Openapi Verifier Laravel Package

radebatz/openapi-verifier

A Laravel package for verifying your API against an OpenAPI specification, helping keep routes, requests, and responses in sync with your docs and catching contract mismatches early in development or CI.

View on GitHub
Deep Wiki
Context7

Getting Started

Install as a dev dependency — this is a test/QA tool, not runtime code:

composer require --dev radebatz/openapi-verifier

You need one thing before it does anything useful: an OpenAPI 3 spec file that actually describes your endpoints. For Laravel, the fastest route is Scramble (php artisan scramble:export --path=openapi.json) or hand-maintained zircote/swagger-php annotations. Commit the spec to the repo — it is the contract the verifier enforces.

The spec may be JSON or YAML. JSON needs nothing extra; YAML requires symfony/yaml, which since 2.2.3 is only suggested. If you point the verifier at a .yaml/.yml file without it, you now get a clear message telling you to install it rather than a cryptic parse failure — so just add it up front if you prefer YAML:

composer require --dev symfony/yaml

Read vendor/radebatz/openapi-verifier/README.md first, then src/ (it's small: a Laravel/Slim test helper, a PSR-15 middleware, and Guzzle/PSR-18 middleware, all thin wrappers around league/openapi-psr7-validator). The wrapper classes are what you'll touch; the league validator is where the actual rules live.

First use case — one feature test proving a response matches the contract:

use OpenAPI\Verifier\Laravel\LaravelVerifier; // verify exact class in vendor/

public function test_users_index_matches_spec(): void
{
    $this->seed(UserSeeder::class);

    $response = $this->getJson('/api/users');
    $response->assertOk();

    (new LaravelVerifier(base_path('openapi.json')))
        ->verify($response->baseResponse, 'GET', '/api/users');
}

If a trait is shipped instead of a class, drop it into a base TestCase so every API test inherits the assertion. Either way, do this on one endpoint before wiring it into CI — you'll immediately discover how far your spec has drifted from reality.

Implementation Patterns

Contract smoke test across the whole API. A data-provider test that hits each route once and asserts the response validates is the highest-value use:

public static function endpoints(): array
{
    return [
        ['GET',  '/api/users'],
        ['GET',  '/api/users/1'],
        ['POST', '/api/users', ['name' => 'Ada', 'email' => '[email protected]']],
    ];
}

Seed fixtures per case and verify. Run this on every PR — it catches removed fields, changed types, and renamed keys that assertJsonStructure never would.

Verify error responses, not just happy paths. Laravel's 422 validation payload (message + errors) and 404 body must be documented in the spec, or the verifier fails. This is where most first-run failures come from — and it's a genuine design win, because undocumented error shapes are a common source of client breakage. Add a case per documented status code.

Guzzle / PSR-18 middleware for outbound APIs. Useful when your app consumes a third-party API that publishes a spec. Attach the middleware to a handler stack and validate every response the client receives:

use GuzzleHttp\HandlerStack;
use OpenAPI\Verifier\Guzzle\OpenApiVerifierMiddleware;

$stack = HandlerStack::create();
$stack->push(OpenApiVerifierMiddleware::create(base_path('specs/billing.json')));

Http::globalOptions(['handler' => $stack]);

$invoice = Http::get('https://sandbox.billing.test/v1/invoices/42')->json();

This gives you early warning that a vendor broke their contract before your code does. Vendor specs are often published as YAML — install symfony/yaml and you can load them directly rather than converting to JSON first.

PSR-15 middleware in staging, never production. Laravel's middleware pipeline is not PSR-15, so this path requires a bridge and is the least convenient option in Laravel. If you want runtime drift detection, run it in a staging environment only — it parses and compiles the spec per request and will cost you latency.

Wrap it once. Put a tests/Concerns/VerifiesOpenApi.php trait around the package (spec path, default method/path extraction from the current test request, sane validation settings). You then have one place to pin version quirks, swap to league/openapi-psr7-validator directly, or add a Pest expectation via expect()->extend('toMatchOpenApiSpec', ...). Make the spec path a single constant so JSON→YAML (or vice versa) is a one-line change.

Keep the spec in the loop. Regenerate/export the spec in CI before the test job, and fail the build if git diff --exit-code openapi.json (or openapi.yaml) is dirty — otherwise the contract silently rots.

Gotchas and Tips

  • YAML needs symfony/yaml, and it's only a suggestion. It won't come in transitively. Since 2.2.3 the package detects the missing dependency and tells you to install it — treat that message as the fix, not a parse bug in your spec.
  • Undocumented status codes fail hard. 422, 429, 401, 500 — Laravel emits all of them without you thinking about it. Document each one or the verifier will flag responses that are perfectly correct.
  • You must convert to PSR-7. The core verifier wants a PSR-7 response; $response->baseResponse is a Symfony response. The Laravel helper presumably handles this — if it doesn't, add symfony/psr-http-message-bridge + nyholm/psr7. This is the most common "why doesn't it work" cause.
  • additionalProperties defaults to true. Extra keys in a JSON object pass silently unless you set additionalProperties: false. Don't assume the verifier catches over-posting of response fields.
  • Format validation is opt-in. format: date-time / email are annotations, not constraints, in the underlying league validator unless you enable format validation via ValidationSettings. If you need RFC 3339 enforcement, configure it explicitly.
  • Type coercion traps. PHP serializes 1.0 as 1 unless JSON_PRESERVE_ZERO_FRACTION is set; big integers and money-as-strings routinely break type: number vs type: string assumptions. Decimals cast to strings in Eloquent are a frequent offender.
  • 204 No Content must have no content block, and response()->json([]) returns {} not an empty body — spec accordingly.
  • Http::fake() bypasses the handler stack, so Guzzle middleware verification and faking are mutually exclusive. Use the verifier in integration tests against a sandbox, fakes in unit tests.
  • Reuse the verifier instance. Parsing and compiling the spec is the slow part; build it once per test class (static property or setUpBeforeClass) rather than per assertion, or your suite will crawl. This matters more with YAML, where parsing pulls in an extra library.
  • Path templating needs real parameters. If your path item declares a required {id} parameter, the verifier wants a matching request path value. Pass the concrete path (/api/users/1), not the template.
  • Exception pages produce HTML. Always use getJson() / withHeader('Accept', 'application/json') in verifier tests, or you'll be validating a Symfony error page against your JSON schema.
  • Maintenance risk is real (small project, few dependents) but actively maintained — 2.2.3 is a patch on a live 2.x line with CI running against that branch. Still: pin the version, keep it in require-dev, and isolate it behind your own trait so swapping to league/openapi-psr7-validator directly is a one-file change.
  • Extension point: everything ultimately delegates to league/openapi-psr7-validator, so ValidatorBuilder and ValidationSettings are your escape hatches for strictness tuning, and the package's middleware factories accept those settings where exposed.
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.
mmccook/php-json-canonicalization-scheme
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
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent