slick/json-api-bundle
A Symfony bundle for building JSON:API-compliant APIs, offering resource serialization, relationship handling, sparse fieldsets, pagination, filtering, and standardized error responses. Integrates with Symfony routing and serializer to simplify spec-compliant endpoint development.
This is a Symfony 7.x bundle, not a Laravel package — so the first thing to check is whether you actually need it. In a Laravel app you'll almost always want the underlying slick/json-api library and a thin controller layer of your own, borrowing the bundle's conventions rather than installing the bundle (it registers Symfony DI extensions and a kernel request listener that won't boot under Laravel's container).
Minimal path for a Laravel developer:
composer require slick/json-api (core encoder/schema classes) — skip slick/json-api-bundle.KernelRequestListener source first; it's the smallest, most instructive file and defines the request/response contract you'll want to mirror.GET /api/posts endpoint returning a JSON:API document with data, type, id, attributes, and links.self. Get that serializing correctly before touching relationships.Because v0.3.0 bumps Symfony to 7.x, any 6.x-era snippets or examples you find are likely stale — check the composer.json constraints of the tag you're reading.
Manual request-param parsing. The bundle's listener maps fields[type], include, sort, page[...], and filter[...] onto the encoder. In Laravel, replicate this in a middleware or a JsonApiRequest value object so controllers stay thin:
$fields = $request->query('fields', []); // ['posts' => 'title,author']
Sparse fieldsets (new in 0.3.0). The listener now honors requested sparse fields when encoding documents. Practically: resolve the field list per resource type, then filter the schema's attribute set before serialization — not after, or you'll leak omitted fields as null and break clients doing strict key checks.
$allowed = explode(',', $fields['posts'] ?? '');
$attributes = array_intersect_key($attributes, array_flip($allowed));
Schema-per-resource. Define one class per JSON:API type with getId(), getType(), getAttributes(), getRelationships(). Register them in a small resolver (a match on type, or a container binding keyed by type string) — that's the Laravel analogue of the bundle's schema registry.
Relationship inclusion as a graph walk. Parse include, split on . for nested paths, and only load relationships that were requested. Eager-load with Eloquent with() derived from the include list to avoid N+1.
Error responses through a single formatter. JSON:API error objects (errors[].status, .code, .source.pointer) should come from one exception renderer, ideally hooked into Laravel's Handler::render. Keep media type application/vnd.api+json on both success and error paths.
Testing contract. Assert exact response shapes with assertJsonStructure plus negative assertions that omitted sparse fields are truly absent.
composer require it into Laravel expecting a service provider. If you must share code, extract the encoder usage, not the bundle.include. Requesting fields[posts]=title while including an author means the author's own fields are governed by fields[people] (or whatever your type is named). Missing type keys should mean "all fields," not "none" — the spec allows either reading, but clients break on inconsistent choices.application/vnd.api+json; sending application/json should yield 415 by spec. Most implementations quietly ignore this — decide deliberately and document it.204 for empty writes, not 200 {}. A common slip when hand-rolling the response layer.application/vnd.api+json; ext=... and profile params are legal; naive === checks on the Content-Type header will misfire.fields[] key or a comma with a space (title, author) will silently produce empty attribute sets.KernelRequestListener is the seam. If you build a Laravel equivalent, keep it as a single invokable class so you can unit-test param handling without booting HTTP.type names carefully. They appear in fields[...] keys, include paths, and relationship data.type — renaming one is silently breaking for clients.How can I help you explore Laravel packages today?