rekalogika/api-lite
rekalogika/api-lite is a lightweight toolkit for building clean, fast JSON APIs with minimal boilerplate. It simplifies resource exposure, serialization, and CRUD endpoints so you can ship well-structured, standards-friendly APIs without the overhead of heavier frameworks.
Pin before you install. This is a pre-1.0 package with a small maintainer surface, so don't let Composer float you across minor versions.
composer require rekalogika/api-lite:0.9.1
Then immediately check what you just agreed to:
composer show rekalogika/api-lite
composer show rekapager # the dependency bumped in 0.9.1
git diff composer.lock
v0.9.1 is a dependency refresh of rekapager — no features, no migrations, no deprecations. That means no code changes for you, but it does mean your pagination behavior is now defined by whatever rekapager version resolved. Check it.
Where to look first, in order:
php artisan vendor:publish and see what appears under config/) — this tells you what the package actually owns: routes, middleware, serialization envelope, pagination defaults.rekapager's own README — pagination semantics live there, not here.First use case: one real CRUD resource end-to-end. Pick something boring and Eloquent-backed — an Article or a Ticket. Don't start with your hardest resource; the point is to measure the boilerplate delta.
Minimal path:
/api/v1 with your auth/throttle/CORS middleware.FormRequest for store/update validation — the package leans on Laravel-native validation, so this should be zero new concepts.curl and php artisan route:list --path=api/v1.If that first resource takes meaningfully longer than hand-rolling a controller plus a paginator, stop — the package isn't earning its keep for your surface area.
Sanity check before you go further: the description frames this as a Laravel package, but the repository and its API Platform lineage read like Symfony/PHP tooling. Reconcile that in the first hour — confirm the supported PHP and Laravel version matrix against your composer.json and your CI matrix. A five-minute compatibility check beats discovering it in the spike.
Keep controllers thin and the domain unaware of the package. The single most important pattern: nothing in app/Models, app/Actions, or your domain services should use a package class. Package types stop at the HTTP boundary.
final class ArticleController extends Controller
{
public function index(ListArticles $list): JsonResponse
{
return $this->apiResponse->paginated(
$list->execute(request()->only('filter', 'sort', 'per_page'))
);
}
}
Centralize the response envelope in one place. Whether you use the package's helper directly or wrap it, funnel every endpoint through a single ApiResponse class (or a base controller method). The envelope is a product contract — consumers depend on data/meta/links and error shapes. One factory means one file to change when you migrate off, and one file to test.
final class ApiResponse
{
public function paginated(AbstractPaginator $p): JsonResponse { /* ... */ }
public function item(mixed $data, int $status = 200): JsonResponse { /* ... */ }
public function error(string $code, string $message, int $status): JsonResponse { /* ... */ }
}
Validation: use FormRequest for everything, including index queries. Laravel-native validation is the package's stated selling point — lean on it. Validate filter, sort, per_page, and cursor params through a request class so that bad input returns a 422 in your standard envelope rather than a 500 from an unguarded query builder.
Whitelist filterable and sortable columns. Do this explicitly in a config array or the form request. Never pass a user-supplied column name into orderBy or a where clause. An unfiltered "filter anything" API is a data-exfiltration oracle and an index-miss generator at the same time.
protected array $sortable = ['created_at', 'title'];
protected array $filterable = ['status', 'author_id'];
Pagination: specify the semantics, don't inherit them by accident. Because rekapager is the moving part, treat cursor vs. offset, cursor encoding, and link generation as a written decision:
ORDER BY created_at, id) — without it, keyset pagination can skip or duplicate rows at page boundaries.rekapager bump changing cursor format.public function test_article_index_pagination_contract(): void
{
$response = $this->getJson('/api/v1/articles?per_page=2');
$response->assertOk()->assertJsonStructure([
'data' => [['id', 'title', 'created_at']],
'links' => ['first', 'next'],
'meta' => ['current_page', 'per_page', 'total'],
]);
}
Middleware stack: compose it, don't assume it. Build the group explicitly so it's readable in routes/api.php: CORS first, then throttle, then auth, then your resource routes. Auth (Sanctum) and throttling are standard Laravel — the package gives you a place to attach them, not the implementations. Confirm preflight OPTIONS requests aren't swallowed by auth middleware.
Version from day one. Mount everything under /api/v1, even for internal APIs. It costs nothing now and buys you a migration path when a 0.9.x minor bump forces a response-shape change.
Testing strategy that scales to many endpoints. For each resource, three tests: happy-path item, happy-path collection with pagination contract, and a validation-failure test asserting your error envelope. That's ~15 lines each and it's what makes future Composer bumps safe.
php artisan test --filter=Article
The pre-1.0 upgrade treadmill is the real cost, not the code. Every 0.9.x minor is a candidate breaking change. Pin the exact version ("rekalogika/api-lite": "0.9.1"), and put "review changelog + run contract tests" into your routine before any composer update. v0.9.1 needs no work — assume the next one does.
A dependency bump is a behavior change until proven otherwise. rekapager is the changed package and pagination is the most contract-visible thing an API does. After bumping, diff the actual JSON for a paginated endpoint, not just the test suite exit code. Watch for changed cursor encoding, changed parameter names, changed links structure, and changed exception behavior on invalid cursors.
Reconcile the documentation against reality before you build. The description reads Laravel-first; the repository's heritage is API Platform. If the install, the namespace, or the concepts don't match what you read, don't paper over it with workarounds — that gap becomes a support burden you own alone.
Don't let Eloquent models reach the wire. Serialize explicit arrays or API Resources. Package serialization that reflects a model will happily leak columns you add later (internal flags, soft-delete markers, foreign keys). An explicit toArray() in a resource class is boring and future-proof.
Error responses are the most-neglected half of the contract. Wire one exception renderer so ValidationException, ModelNotFoundException, AuthenticationException, and ThrottleRequestsException all produce your envelope with a stable code and message. Test them. Consumers integrate against your 404 shape far more often than you'd expect.
Middleware ordering bites in two specific places: CORS preflight must succeed before auth rejects the request, and throttling after auth gives you per-user limits instead of per-IP. Decide deliberately and write a test for the preflight.
Debugging checklist when a response looks wrong:
php artisan route:list --path=api/v1 — is your route even the one being hit, or did a package route shadow it?php artisan config:clear && php artisan route:clear — published config/route caches are the classic "it works locally" culprit.ApiResponse factory, not inside the package — if the wrong thing reaches your factory, the problem is upstream in the controller; if it leaves the factory wrong, the problem is yours.report()); a silently-empty data array is usually a swallowed exception in a package helper.Extension points worth knowing before you need them: published config (defaults for per-page, max per-page, envelope keys), container bindings for the response factory and paginator, and the exception renderer. Binding your own ApiResponse implementation through the container is the cheapest escape hatch if the package's shape diverges from what your consumers need.
Keep the abstraction thin but real. One ApiResponse class plus an allowlist config is the whole insulation layer. That's enough that swapping to hand-rolled pagination later is a contained two-file change instead of a codebase-wide grep.
Bus factor is an engineering risk with an engineering mitigation. Pre-1.0, tiny maintainer surface, no LTS or security-response commitment. Mitigate by using it on internal, first-party, CRUD-shaped surfaces; keep it out of revenue-critical or partner-negotiated contracts; and never let it become a platform-level dependency your other packages depend on.
Re-run the go/no-go after the spike. Compare the first resource against the hand-rolled baseline: lines of code, time to first endpoint, and how confident you are that the pagination contract is correct. If the delta is small, two controllers and a paginator are cheaper than the upgrade treadmill — and there's no shame in walking away at that point.
How can I help you explore Laravel packages today?