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 Client Laravel Package

art4/json-api-client

A PHP library for parsing and validating JSON:API documents. It turns responses into typed resources, relationships, links, errors, and meta objects, helping you build reliable JSON:API clients without manual array handling.

Deep Wiki
Context7

Getting Started

art4/json-api-client is a parser/validator only — it does not make HTTP calls. You bring your own PSR-7/PSR-18 client (Guzzle, Laravel's Http, Symfony HttpClient) and hand the raw body to the parser.

Minimal steps

  1. Install: composer require art4/json-api-client (check the PHP constraint in composer.json; 4.x targets PHP 7.2+/8.x).
  2. Send your request however you like and get the response body as a string.
  3. Parse it:
use Art4\JsonApiClient\Helper\Parser;

$body = Http::withHeaders(['Accept' => 'application/vnd.api+json'])
    ->get('https://api.example.com/articles/1')
    ->body();

$document = Parser::parse($body);           // returns DocumentInterface
  1. Navigate the document:
if ($document->has('data')) {
    $article = $document->get('data');       // ResourceObject
    $type    = $article->get('type');        // "articles"
    $id      = $article->get('id');          // "1" (string per spec)
    $title   = $article->get('attributes')->get('title');
}

Where to look first

  • The docs/ folder / README usage blocks — the element hierarchy (Document, ResourceObject, ResourceIdentifier, Relationship, Error, Link) is the mental model you need.
  • tests/ — the fastest way to see how each member (links, meta, included, errors) is represented.
  • If you're on an older 3.x tutorial, verify method names; the 4.x API moved to Helper\Parser.

First real use case: a "show" endpoint (GET /articles/1?include=author). You'll parse the document, read data.attributes, then resolve data.relationships.author.data.id against included to hydrate the author.

Implementation Patterns

1. Wrap it in a dedicated client/service. Don't scatter Parser::parse() through controllers. Create App\Services\JsonApiClient that owns the HTTP layer, headers, base URL, auth, and returns typed results. This also isolates you from the package's low bus factor (138 stars, GPL license).

final class JsonApiClient
{
    public function get(string $path, array $query = []): DocumentInterface
    {
        $response = Http::withHeaders([
            'Accept' => 'application/vnd.api+json',
        ])->get(config('services.jsonapi.base_url').$path, $query);

        $response->throw();                    // transport/5xx first
        return Parser::parse($response->body()); // then document validation
    }
}

2. Map resource objects to DTOs. JSON:API's nesting is noisy; don't leak ResourceObject into your domain.

$attrs = $document->get('data')->get('attributes');
$article = new ArticleDto(
    id:    $document->get('data')->get('id'),
    title: $attrs->get('title'),
    body:  $attrs->get('body'),
);

3. Resolve included resources yourself. Relationships give you identifiers, not the object. Build a lookup keyed by "type:id" and join in the mapper.

$included = [];
if ($document->has('included')) {
    foreach ($document->get('included') as $resource) {
        $included[$resource->get('type').':'.$resource->get('id')] = $resource;
    }
}

$rel = $article->get('relationships')->get('author');
if ($rel->has('data')) {
    $identifier = $rel->get('data');               // may be null or a collection
    $author = $included[$identifier->get('type').':'.$identifier->get('id')] ?? null;
}

4. Pagination from links + meta. Cursor/page numbers aren't standardized in the body — they live in links.next, links.prev, and meta. Read them defensively:

$next = $document->has('links') && $document->get('links')->has('next')
    ? $document->get('links')->get('next')
    : null;
$total = $document->has('meta') ? $document->get('meta')->get('total') : null;

5. Error mapping to Laravel exceptions / validation messages. Distinguish three layers: HTTP failure, JSON:API errors payload, and parse/validation failure. For the second:

if ($document->has('errors')) {
    foreach ($document->get('errors') as $error) {
        report(new JsonApiException(
            title:  $error->get('title'),
            detail: $error->get('detail'),
            status: $error->get('status'),
            source: $error->has('source') ? $error->get('source') : null,
        ));
    }
}

422 responses from a compliant server are data, not transport errors — don't let ->throw() swallow them before you parse. Check $response->status() and parse the body for both success and 4xx where a JSON:API document is expected.

6. Writes (POST/PATCH/DELETE). This library is read-oriented; you'll typically assemble the JSON:API request body as a PHP array and json_encode it, sending Content-Type: application/vnd.api+json. Confirm in the docs whether your version ships serializer helpers before hand-rolling one — keep that serialization in your JsonApiClient wrapper either way.

7. Testing. Use Http::fake() with recorded JSON:API fixtures (success, sparse fieldsets, included, error documents, 204 No Content) and assert your DTOs. Parsing is pure, so mapper tests need no HTTP at all.

Gotchas and Tips

  • License is GPL-3.0. For a commercial, distributed Laravel product this is the single biggest adoption blocker — GPL can be read as viral for linked/distributed code. Get legal sign-off, or evaluate MIT alternatives such as swisnl/json-api-client and keep your adapter thin so swapping is a one-file change.
  • It is not an HTTP client. All the transport concerns (retries, auth, Accept/Content-Type headers, timeouts) are yours. The library only validates and exposes document structure.
  • Strict validation can throw on "almost valid" documents. Servers that omit required members or use non-spec constructs will cause parse exceptions. Log the raw body alongside the exception — otherwise you'll be debugging blind in production.
  • data has three shapes: null, a single resource object, or an array (collections). Always branch on the endpoint or inspect the type before calling get('id'); a null data (e.g., 404-style "missing" documents, or to-one relationships with no link) will otherwise fatal.
  • relationships entries may lack data. A relationship can carry only links and/or meta (the "related link only" pattern). Use $relationship->has('data') before get('data').
  • IDs are strings. The spec mandates string IDs; don't (int) them silently if you key caches or database lookups on them.
  • Never parse an empty body. 204 No Content and 304 responses have no document; Parser::parse('') will fail. Guard on trim($body) !== ''.
  • Use has() everywhere. Missing-member access throws an access exception rather than returning null, which is easy to trip over in optional attributes.
  • included can be large. There's no streaming; a heavily-included document is fully materialized. Watch memory on list endpoints, and prefer fields[...] sparse fieldsets and targeted include parameters.
  • Sparse fieldsets change your mapper's assumptions. A field present in one response may be absent in another; make DTO mapping tolerate missing attributes or fail with a clear domain error.
  • Content negotiation is not enforced for you. Send Accept: application/vnd.api+json; a server returning application/json may still parse structurally, masking a contract violation.
  • Extension point for debugging: a "raw body + parsed tree" log channel in your wrapper (dev only) is worth the ten lines — it turns most AccessExceptions into a five-second diagnosis.
  • Pin the version and keep a compatibility test suite. Low release cadence and star count mean API shifts between majors are real; a handful of fixture tests around Parser::parse() protects you during upgrades.
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle