lara-spec-first
A Spec-First API framework and integration layer for Laravel. Define your contracts with OpenAPI, generate stubs for AI, mock endpoints with Faker, and bridge legacy code.
Why lara-spec-first?
Start with your OpenAPI contract (YML file), and let lara-spec-first handle the routing, validation, and skeletal architecture while letting your business logic live safely in standard Laravel controllers.
Documentation
gcob.github.io/lara-spec-first publishes every page under docs/, with search and a sidebar. It is the same content this repository carries, so each link below works from either one.
Key Features
- Contract-Driven Routing: Automatically register routes based on your OpenAPI specification.
- Legacy Friendly: Transitional migration path (extend generated base controllers) to adopt Spec-First route by route without breaking existing apps. Already Code-First? Phase 3 will bootstrap your spec from the code you already run. See the Roadmap.
- Instant Mocks: Fallback to automatic Faker-powered responses if the concrete implementation isn't written yet.
- Built for AI-assisted coding, and we say it out loud: a stated goal, not a side effect. Every generated file explains itself: where in the contract it came from, what the build worked out while emitting it, and an
@seeat the code that actually runs. Your agent shouldn't have to guess. Seecode-generation/generated-file-anatomy.md. - Safe to regenerate: Generated code and your code never share a file, so the build can be re-run at any time without losing work, and a contract change surfaces as a static analysis error, not a production incident. See
docs/guide/code-generation/index.md. - Plays well with your formatter, and tell it to skip the generated tree anyway. What the build emits is already canonical under Pint's
laravelpreset, so most projects need to do nothing. Add"exclude": ["app/Http/Generated"]to yourpint.jsonregardless: a formatter and a build that both rewrite one file undo each other forever, and no generator can be canonical under every rule set. Two lines of reasoning, one line of config: your formatter and the build.
Installation
PHP 8.3 or newer, Laravel 12 or 13. The full matrix is below.
composer require gcob/lara-spec-firstThe service provider is discovered automatically. Publish the configuration only when you need to change something in it, since the package merges its own defaults underneath whatever you publish:
php artisan vendor:publish --tag=lara-spec-first-configPoint it at your contract. The default is openapi.yaml at the root of your application, and config/lara-spec-first.php is where you change that.
Then read your contract before generating from it:
php artisan spec:doctor # reports what this package will and will not honor
php artisan spec:build # writes the routes and one controller per operationThat order is worth keeping on a contract this package has never read, and not because building is risky.spec:build plans every file in memory before writing any of them, so a document it refuses leaves your working tree exactly as it was. What it will not do is tell you everything at once: it stops at the first fault and only counts the rest, and it says nothing at all about the constructs it will simply not act on. Reporting both, in one pass, is the doctor's entire job.
spec:build writes only inside app/Http/Generated, never outside it, so the first run cannot touch anything you wrote. Every operation answers 501 until you implement it, and the build names the command that implements each one.
Two things to do before your second build:
- Ignore the generated tree, the way you ignore
vendor/. Addapp/Http/Generated/to your.gitignore, andphp artisan spec:buildto your deploy. See which generated code is committed. - Tell your formatter to skip that tree. Add
"exclude": ["app/Http/Generated"]to yourpint.json. A formatter and a build that both rewrite one file undo each other forever, whatever formatter you run. See your formatter and the build.
Every command, flag and exit code is in docs/guide/commands.md, and what changed in each version is in CHANGELOG.md.
Roadmap
We are building in public! Check out our Roadmap to see where the project is heading, and CONTRIBUTING.md for how to get involved.
The project is in early bootstrap (Phase 1)
Stack & Philosophy
- Spec-First, always. The OpenAPI contract is the source of truth, and PHP follows from it, never the other way around.
- Opinionated, and we own it. Where the specification leaves a choice open, we make one and state it rather than adding a config flag for every fork in the road. A stated opinion you can plan around beats a flexible behavior nobody can predict.
- Parser:
devizzent/cebe-php-openapi, a drop-in fork ofcebe/php-openapithat adds OpenAPI 3.1 support (upstream targets 3.0.x only). Modern design tools export 3.1, so we parse 3.1. - Parsing is not honoring. We read 3.0.x and 3.1.x; we honor a documented subset of what they allow, and we say so out loud rather than ignoring a contract in silence. What is honored, what is not, and why, lives in
docs/guide/openapi-support.md. - Two goals, stated together: developer experience and AI-assisted coding. They pull in the same direction more often than they conflict. What an agent needs is what a new teammate needs, made explicit instead of assumed. Where the OpenAPI ecosystem never standardized something, we would rather be flexible and pleasant than literal and rigid.
- Testing: Pest, with
Spectatorfor contract testing. - AI-Assisted Development: this repository is itself built with transparent AI workflows, a separate claim from the goal above, which is about your project. See
AGENTS.md.
Every technology choice, its status, and the reasoning behind it live in docs/project/stack.md.
Requirements
| Supported | |
|---|---|
| PHP | 8.3, 8.4, 8.5 |
| Laravel | 12.x, 13.x |
Development Environment
See CONTRIBUTING.md.
License
The MIT License (MIT). Please see License File for more information.