lomkit/laravel-rest-api
lomkit/laravel-rest-api is a Laravel package for quickly building production-ready JSON APIs. Define resources declaratively and get automatic CRUD endpoints, powerful filters, sorting, pagination, relationship includes, validation, authorization, and OpenAPI docs while staying close to Laravel conv...
Security note for this release: v2.23.2 is a security patch that accompanies advisory GHSA-w87g-c4fx-87gg. It is a patch-level bump from v2.23.1 with no documented API changes, so treat it as a mandatory in-place upgrade: bump the constraint, run composer update lomkit/laravel-rest-api, then run composer audit and read the advisory to understand which exposure (endpoint class, query parameter, or instruction) it covers. If your API is publicly reachable, prioritise this before other work and confirm your composer.lock is committed to the fix.
Installation is a standard Laravel package flow:
composer require lomkit/laravel-rest-api
php artisan vendor:publish --tag=rest-api-config # confirm tag with `php artisan vendor:publish` listing
The mental model is: you describe what an endpoint exposes with static properties on a controller, and the package writes the Eloquent query, validation, and authorization for you. There is no per-endpoint index()/show() boilerplate — you declare $search, $sort, $filters, $includes, $aggregates, $instructions, and mutation rules, and the package compiles the incoming query string into a scoped, policy-checked query.
First use case: expose a read-only GET /api/users that supports ?search=, ?sort=-created_at, ?filters[...], and ?includes=posts. Create a controller for the User model, list the allowed fields on each surface, register the route, and hit it with a few query strings. That one endpoint exercises the core of the package: field allow-listing, relation includes, and policy enforcement. Run php artisan route:list and inspect the generated routes before wiring anything to a frontend.
Where to look first: the package's own generated controllers (use its artisan generator if available — check php artisan list | grep rest in your version) rather than hand-writing from the README, since the generated skeleton reflects the exact property names in your installed version. Second, the config file, then the tests directory of the package on GitHub for edge-case behaviour.
Declare the surface, don't code it. Every controller is an allow-list. Treat $search, $sort, $filters, $includes, and $aggregates as a security boundary, not a convenience list — anything you name there becomes remotely queryable.
Resource/field classes for output shape. Define resources and typed fields rather than returning models directly, so serialization stays stable when columns change. Keep field definitions close to the model they describe and reuse them across list and detail endpoints.
Relations as first-class objects. Register HasMany/BelongsTo/MorphMany-style relation definitions and expose them through $includes. This is where the package earns its keep versus hand-rolled when($request->has('include')) spaghetti — nested includes and relation-level filters/sorts come for free once the relation is declared.
Authorization via policies and gates. Wire each endpoint to Laravel policies/gates so the package performs per-model checks during query building and mutation. This is safer than controller middleware, because it also guards relation traversal and includes — a policy on Post prevents leaking posts through ?includes=posts on a User resource.
Mutations through the handler, not custom actions. Use the mutating handler with Laravel validation rules and per-operation gates (store/update/destroy). Keep business logic in model events, observers, or dedicated actions invoked after the handler, so validation and authorization stay declarative.
Filters for anything non-trivial. Custom filter classes are the right extension point for scoped queries (?filters[active]=1), multi-tenancy scoping, and search-across-relations. Avoid overriding the query builder globally; a single global scope change can silently alter every endpoint.
Integration tips: register the API routes under a versioned prefix (/api/v1) from day one; put the package routes inside your auth:sanctum group rather than adding middleware afterwards; cache reference endpoints with a short TTL plus Cache-Control since the package does not do HTTP caching; and write feature tests against real HTTP query strings ($this->getJson('/api/users?sort=-id&includes=posts')) — that is the only reliable regression net for query-string behaviour across upgrades.
^2.23.2 (not ^2.23) and add composer audit to CI. Re-read the advisory before assuming your endpoints were affected — the impact depends on which instruction or parameter is involved.$includes but forgetting the policy on the related model is the classic leak. Audit with a checklist: for each controller, every entry in $search/$sort/$filters/$includes/$aggregates must be backed by a policy or an explicit scope.?includes=posts.comments.author) multiply queries. Cap nesting, enforce perPage maximums, and enable Eloquent strict mode / preventLazyLoading in non-production to surface it early.$sort accepts only what you list, but each listed column still needs an index; sorting on email is both slow and an enumeration vector. Consider excluding columns you don't want ordered.php artisan config:clear) after publishing, or a Route::apiResource collision with your own routes. Route names generated by the package can clash across controllers — check route:list and rename where needed.LIKE. $search typically ORs across the listed fields with %term%; on large tables this is a full scan. Swap to a Scout/full-text filter for anything past a few hundred thousand rows.DB::listen) when a filter behaves unexpectedly; most "the package ignores my filter" reports are a typo between the filter's public name and the ?filters[...] key, or a missing entry in the controller's allow-list.How can I help you explore Laravel packages today?