Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Json Api Laravel Package

slick/json-api

A lightweight Laravel package for building JSON:API-compliant APIs with resource serialization, relationships, sparse fieldsets, filtering, pagination, and validation error handling—conventions baked in, minimal configuration required.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require slick/json-api

This is a framework-agnostic JSON:API (v1.0) implementation from the Slick micro-framework ecosystem, not a Laravel package. There is no service provider, no facade, and no published config, so expect to wire it into Laravel yourself. Before writing anything, read the package source and its test suite — with one star and zero dependents, the tests are effectively the documentation.

Where to look first, in order:

  1. src/ for the document model — classes named around Document, ResourceObject, Relationship, Error, and Pagination (names shift between releases; confirm against the installed version).
  2. src/ for the schema layer — v1.5.1 added DI container support to schema discovery, so there are discoverer/registry classes that build schema objects for you. Read these before hand-rolling your own mapper; they may already cover type/attribute/relationship declaration.
  3. tests/ for end-to-end examples of building a document and serializing it.
  4. Any composer.json suggest/require entries to see which PSR-7/PSR-17 implementation it expects, and note the doctrine/collections constraint. Most of these libraries build on PSR-7 messages rather than returning Laravel Response objects.

First use case — return a single resource from a controller:

public function show(int $id)
{
    $doc = $this->documents->create()
        ->withData('articles', (string) $id, [
            'title' => $article->title,
        ]);

    return response($doc->toJson(), 200)
        ->header('Content-Type', 'application/vnd.api+json');
}

Get that working before touching relationships, includes, or pagination. The value of the package is spec-correct document shape (the data/included/links/meta envelope, string IDs, application/vnd.api+json), so validate against a JSON:API client or the spec examples early.

Implementation Patterns

Bind the document builder as a singleton. Instantiate the document builder once in a service provider and inject it into controllers/responders. Each action composes data, relationships, and links. Keep serialization in a thin responder class; the package handles structure, not your domain mapping.

Hand the discoverer Laravel's container. Since v1.5.1, schema discovery resolves dependencies through a PSR-11 container. In a service provider, pass app() (Laravel's container implements PSR-11) into the discoverer so schema classes can constructor-inject repositories, formatters, or config. This is the single most useful integration point the package now offers — register schemas as container-resolved services rather than new-ing them:

$this->app->singleton(SchemaDiscoverer::class, function ($app) {
    return (new SchemaDiscoverer())->withContainer($app); // exact API: check v1.5.1 source
});

Schema-per-resource, discovered rather than hand-built. Declare a schema class per JSON:API type (attributes, relationships, ID accessor) and let discovery assemble them. This gives you one place to enforce string IDs and it pairs naturally with the container for resolving mappers. If your installed version's discoverer API differs from what you expect, write an adapter interface in your app so upgrades touch one file.

Eloquent-to-resource mapping. The package has no Eloquent awareness, so write one method per model that returns type, string-cast id, attributes, and relationships. Cast every primary key and foreign key with (string) — JSON:API requires string IDs and clients will break on integers.

Compound documents via included. When a request carries ?include=author,comments, resolve the requested relationships yourself, deduplicate the related resources by (type, id), and pass them as the included set. JSON:API forbids duplicate resource objects in included; a naive array_merge across eager-loaded relations is the usual source of that bug. Note the package uses doctrine/collections for its collections — build ArrayCollections and use ->unique()/->map() rather than plain arrays where the API expects them.

Pagination through the package's paginator, driven by page[number]/page[size]. Read the query params with defaults, hand the total/offset/limit to the paginator so it emits links.first/prev/next/last and meta, and keep the actual Eloquent forPage() call in your repository. Don't hand-build the link URLs — let the paginator do it so the query string survives.

Error handling as a boundary concern. Map exceptions to JSON:API error objects in App\Exceptions\Handler (or a dedicated middleware): validation → 422 with source.pointer set to the offending field, missing model → 404, auth → 401/403. Remember JSON:API requires status to be a string, and errors must be an array even for a single error. The package's error classes give you the shape; the HTTP status still has to be set by you.

Content negotiation middleware. Enforce Accept: application/vnd.api+json and return 406 on mismatch, and require Content-Type: application/vnd.api+json on writes (415 otherwise). This is exactly what the package advertises, but in Laravel it belongs in middleware wrapping the API route group — check whether the package ships negotiators you can reuse rather than reimplementing the media-type parsing.

Sparse fieldsets. If fields[articles]=title,author is present, filter attributes and relationships before serializing. Because the package is document-oriented, this is a projection step you own; do it on the attribute/relationship collections, not on the Eloquent query, to keep it simple.

CRUD conventions. Follow JSON:API's status codes rather than Laravel's defaults: 201 with a Location header on create, 204 with no body on delete, and on update return 200 with the full updated resource (or 204 if the client sent no body expectations). The package makes the body easy; the headers are on you.

Gotchas and Tips

  • Adoption risk is still the headline. Zero dependents, one star, and a NOASSERTION license mean no community answers and legal review before production. Pin an exact version ("slick/json-api": "1.5.1") in composer.json, and consider vendoring or forking if you depend on it heavily. Read the license file itself; NOASSERTION from Composer means it could not match a known SPDX identifier, not that it is unlicensed.
  • doctrine/collections is now ^2.2 || ^3.0. Before v1.5.1 you were effectively pinned to the 2.x line; if anything else in your app constrains doctrine/collections to ^1.0 (some older Doctrine ORM setups do), Composer will resolve to a mutually compatible version and you may end up on 1.x, silently exercising an untested path. Run composer why doctrine/collections and confirm which major you actually installed.
  • Doctrine Collections 3.0 API differences. If you write code against the package's collections (ArrayCollection, Collection), check the v3 signature changes — notably tightened parameter/return types on contains(), remove(), and indexOf(), and stricter generics/template annotations. Test the serialization path specifically, since type errors there surface as 500s with unhelpful traces.
  • Container support can hide state. The discoverer now resolves dependencies through the container, which means singletons you register are shared across discovered schemas. If a schema holds per-request state (the current user, the requested include list), do not resolve it as a singleton — resolve it per request, or keep request-specific data out of the schema object.
  • Version churn. Verify the v1.5.1 tag exists on the remote and that its PHP and Symfony component constraints match your runtime (the release refreshed Symfony deps). Namespace and class names have historically shifted in Slick packages — write a thin adapter layer around the package so an upgrade touches one file.
  • The media type is not optional. application/json in either direction is a spec violation. Browsers and Postman send */* by default, so make the middleware permissive enough for local debugging but strict in production.
  • String IDs everywhere. Also true for relationship linkage objects: {"type":"people","id":"1"}, never 1. This is the single most common cause of "it works in my test but not in the client."
  • No Laravel validation integration. ValidationException renders as Laravel's own JSON unless you intercept it. Convert $e->errors() into JSON:API error objects with source.pointer paths like /data/attributes/title.
  • Check how it expects request input. If the package parses JSON:API request bodies (data.attributes nesting), do not also run Laravel's $request->validate() on the flat payload — validate the unwrapped attributes, or you'll get confusing "field is required" failures.
  • Serialization and JsonSerializable. Confirm whether toJson() handles JSON_UNESCAPED_SLASHES/JSON_UNESCAPED_UNICODE and throws on failure. If not, wrap it and use JSON_THROW_ON_ERROR in your own encoder.
  • Extension points are improving but thin. Container-backed schema discovery is a real hook; there is still no event system or macroable registry. Customization means subclassing the document/resource/schema classes or composing around them. Prefer composition — a ResourceMapper and a DocumentResponder in your app — so the package stays replaceable.
  • Debugging tip. When a client rejects a response, diff your payload against the spec's example documents rather than against the package's output; a self-consistent but non-conforming library will pass its own tests.
  • Test contract, not implementation. Write feature tests asserting the JSON:API envelope (top-level data/errors, string IDs, Content-Type, status-as-string) so a package swap is verifiable. Given the maturity level, treat the package as an internal detail, not a foundation.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons