dskripchenko/laravel-api
Laravel package for rapidly building REST APIs with automatic routing, request validation, filtering, pagination, and consistent JSON responses—plus OpenAPI docs generation to keep your endpoints documented as they grow.
Install and publish.
composer require dskripchenko/laravel-api
php artisan vendor:publish --tag=laravel-api-config
You should end up with a config/api.php (or similarly named) file that controls route prefixing, version list, docs route, and middleware groups. Read that file top to bottom before writing any code — for a package this small, the config is the documentation. The second place to look is the vendor directory: vendor/dskripchenko/laravel-api/src/ will show you the route registrar, the OpenAPI generator, and the CRUD base classes. With 13 stars and no meaningful ecosystem, source-reading is the primary API reference.
First use case: one versioned endpoint with docs.
v1) in config, pointing at a controllers namespace and a route file./api/docs or whatever the config exposes) and confirm the operation appears.The value proposition lands on step 4: if the OpenAPI spec renders without you writing a single YAML file or #[OA\...] attribute, the package is doing its job. Do this in the first ten minutes; if the spec is empty or malformed, you've learned the package's limits early and cheaply.
Versioning mental model. Treat a version as a frozen contract. New version = new controller namespace + new route file + new config entry, sharing models and services underneath. Don't try to make one controller serve two versions with conditionals; that's how you get a spec that lies.
Version-per-namespace, shared domain layer.
app/Http/Api/
V1/
Controllers/UserController.php
Routes/api.php
V2/
Controllers/UserController.php
Routes/api.php
V2's UserController can extends V1's and override only the changed actions. This keeps duplication low while keeping the contract per version explicit — which is exactly what the spec generator needs to emit two distinct documents.
PHPDoc as the single source of truth.
/**
* List users for the current account.
*
* @queryParam page integer Page number. Example: 1
* @queryParam per_page integer Items per page. Example: 20
* @response 200 {"data": [{"id": 1, "name": "Ada"}]}
* @response 403 {"message": "Forbidden"}
*/
public function index(IndexUsersRequest $request): UserCollection
{
return new UserCollection(User::paginate($request->per_page ?? 20));
}
Rules that keep this sustainable:
CRUD scaffolding. Use the generator for resources that are genuinely CRUD (lookup tables, simple admin resources) and hand-write anything with real business rules. Scaffolding pays for itself on resource #5 and #6; it costs you a rewrite on resource #1 if the domain is non-trivial. Run the generator, then immediately delete the actions you don't expose — dead generated endpoints become dead documented endpoints.
Middleware cascades. This is the feature to lean on hardest. Compose security per layer rather than repeating it per route:
Because the cascade is declared in one place, a new endpoint inherits the right posture by default. Audit the cascade whenever you onboard a new controller — an inherited middleware you didn't expect is the most likely source of "why is this 403?" bugs.
CI integration. Generate the spec in CI and diff it against the committed artifact. Any undocumented change or accidental contract break becomes a failing build rather than a Slack thread three weeks later. Also point frontend codegen (openapi-generator, orval, etc.) at the generated file — that's where the effort actually pays back.
Testing. Use the generated spec to drive a contract test: assert every documented response code is actually reachable, and assert no route exists without a documented operation. A one-off test, run in CI, catches the vast majority of drift.
Small-package risk. 13 stars, one author, a recent release date. That means: no StackOverflow corpus, no blog posts to crib from, and a real bus-factor. Before adopting, read the generator source and confirm you could fork and maintain it. Pin the version in composer.json; don't ride dev-main.
PHPDoc is not validated at runtime. Nothing stops you from shipping a docblock that says @response 200 while the controller returns 422. Treat the spec as generated claims, not a guarantee — hence the contract test above.
Parsing edge cases. Hand-rolled PHPDoc parsers typically choke on: multi-line JSON examples, nested generics like array<int, UserResource>, union types, and anything with a } early in the line. Keep examples on one line, or use a fenced/explicit block if the package supports it. When output looks wrong, bisect your docblock down to a minimal case before assuming a package bug.
IDE and static analysis friction. Docblock-driven contracts can confuse PHPStan/Psalm if your @response examples parse as PHP. If they collide, move the examples into a @response schema reference plus a fixture file rather than inlining JSON.
Versioning pitfalls.
api/v1 collides with an existing routes/api.php, decide early which one owns the prefix, or you'll get 404s that look like middleware failures.Debugging checklist when the spec is empty or wrong:
php artisan route:list first, always.#[Attribute] or blank line breaking association?Extension points worth knowing. Find the class that renders the spec and the base CRUD controller; those are the two places you'll want to subclass or swap in a service provider — for custom error envelopes, auth scheme registration (bearerAuth in components/securitySchemes), and server URL setup. Do it once in a provider rather than per-controller.
When not to use this. If your team already has dedoc/scramble or knuckleswtf/scribe working, the marginal gain is the versioning + middleware cascade, not the docs generation. And if your API is small and single-version, the scaffolding won't pay for the extra indirection — plain Laravel routes plus Scramble will be less machinery for the same result.
General rule for this package: it's a convention-and-codegen tool, so it rewards strictness. Uniform naming, uniform envelopes, docblocks written before bodies, and version directories that never get retro-edited. Every bit of inconsistency you tolerate will show up as a wrong or missing line in the published spec.
How can I help you explore Laravel packages today?