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

tobscure/json-api

A PHP library for building JSON:API-compliant APIs. It handles resource serialization, relationships, included data, links, pagination, and request parsing, making it easy to add a spec-compliant JSON API layer to Laravel or any PHP application.

Deep Wiki
Context7

Getting Started

Install and pin. The package is archived and the last tag is from 2017, so pin an explicit version rather than floating:

composer require tobscure/json-api:^0.5

Check the tag you actually got: early 0.x releases used Tobscure\JsonApi\Serializer\AbstractSerializer, while the later API uses Tobscure\JsonApi\Resource. The README on the tag you install is the source of truth for method signatures — don't mix examples from different versions.

Where to look first. The whole package is a thin serialization layer over four concepts:

Class Responsibility
Document The top-level JSON:API envelope (data, included, meta, links, jsonapi)
Resource Serializes one model: type, id, attributes, relationships
Collection Serializes an array of models through a resource
Parameters Parses include, fields, sort, page, filter from query params

First use case — expose an Eloquent model. Subclass Resource, then wrap a model in a Document:

use Tobscure\JsonApi\Resource;
use Tobscure\JsonApi\Document;
use Tobscure\JsonApi\Parameters;

class UserResource extends Resource
{
    protected $type = 'users';

    public function getId($user)
    {
        return (string) $user->id;   // JSON:API ids are strings
    }

    public function getAttributes($user)
    {
        return [
            'username' => $user->username,
            'joined'   => $user->created_at->toIso8601String(),
        ];
    }
}

// In a controller
$params   = new Parameters(request()->query());
$document = new Document(new UserResource($user));

return response()->json($document->toArray(), 200, [
    'Content-Type' => 'application/vnd.api+json',
]);

That gives you a spec-shaped single-resource response. Add a Collection for index endpoints and a getRelationships() method once you need ?include=.

Implementation Patterns

One resource class per model, named after the type. Keep serialization out of controllers and models. A UserResource, PostResource, etc., gives you a single place to change the public contract and makes include traversal trivial because relationship resources are just other resource instances.

Relationships return resource objects, not arrays. getRelationships($model, $include) should return a map of relationship name to Resource/Collection. The Document then uses the requested include paths to decide what lands in included. Pass the remaining include path down so nested includes (author.posts) work:

public function getRelationships($post, $include)
{
    $include = $include ?: [];

    return [
        'author' => new UserResource($post->author),
        'tags'   => new Collection($post->tags->all(), new TagResource(new Tag)),
    ];
}

Translate include into eager loads — this is the real day-to-day win. The most common production bug with JSON:API is N+1 queries. Map the requested include paths onto Eloquent relations and load them before building the document:

$params  = new Parameters(request()->query());
$include = $params->getInclude();          // ['author', 'tags']

$posts = Post::query()
    ->with(array_map(fn ($path) => str_replace('.', '.', $path), $include ?: []))
    ->paginate(20);

Keep an allow-list of legal include paths and reject unknown ones — the library will happily try to serialize whatever you hand it, and a typo in ?include= otherwise silently produces empty included.

Honor sparse fieldsets. $params->getFields() returns something like ['users' => ['username', 'email']]. Apply it in getAttributes() (or in a base resource) by intersecting keys. Without this, adding a field to a resource immediately bloats every response, and you lose the main reason to use JSON:API at all.

Pagination is yours to implement. The package serializes links; it does not paginate. Build links.first/prev/next/last and a meta.page block from Laravel's paginator and pass them to the Document. Do the same for sort and filter — Parameters parses them, but the query logic is application code.

Centralize the HTTP response. Register a response macro or a small responder service so every endpoint returns the correct media type and status:

Response::macro('jsonApi', function ($document, int $status = 200) {
    return response()->json($document->toArray(), $status, [
        'Content-Type' => 'application/vnd.api+json',
    ]);
});

// return response()->jsonApi(new Document(new PostResource($post)));

Test the array shape, not the string. $document->toArray() is the useful assertion surface — compare against assertJsonStructure/assertJsonFragment for data.type, data.id, data.attributes, and included.*.type. Snapshotting raw JSON makes attribute reordering look like a regression.

Error handling. Wrap validation and not-found exceptions into JSON:API error objects (errors[] with status, title, detail, source.pointer). The package's focus is documents, not exception rendering, so add a Laravel exception handler branch that emits the error envelope with the same content type.

Gotchas and Tips

The media type is part of the contract. Anything other than application/vnd.api+json will break strict clients. Also handle the Accept header on the way in if you support content negotiation; the package does not do this for you.

IDs are strings, always. Casting (string) $model->id in getId() avoids clients that key caches by type+id and choke on mixed int/string keys. Do this once in a base resource.

Archived means frozen. No fixes for PHP 8 deprecations, no typed-property support, no security backports. Verify it runs on your PHP version before committing; if you're on modern Laravel, weigh this package against actively maintained alternatives (laravel-json-api, spatie/laravel-fractal) — especially since JSON:API 1.1 has moved on since 2017.

Attributes must be a flat, plain array. Don't leak Eloquent models, dates, or nested arrays into getAttributes() — the spec expects scalars. Format dates explicitly (toIso8601String()) rather than relying on model casting, which changed significantly across Laravel versions.

include is user input. Never pass it straight into with(). Validate against an allow-list, cap nesting depth, and cap the number/count of included resources. An unbounded ?include=a.b.c.d... is a cheap denial-of-service on a large graph.

Empty collections. new Collection([], new PostResource(new Post)) still needs a "prototype" resource so the serializer knows the type. Passing null or [] incorrectly produces a data: null where clients expect data: [].

Relationship linkage vs. inclusion. getRelationships() controls the relationships block; whether the related resource is also emitted under included depends on the include parameters reaching the Document. If links appear but included is empty, the parameters weren't threaded through — that's the first place to debug.

Config quirks. There is effectively no config file; behavior lives entirely in your resource classes and whatever you pass to Document/Parameters. That's a feature for debugging (grep for new Document) and a hazard for consistency (define a base resource class early and standardize getId, links, and meta there).

Extension point to lean on. Override getLinks($model) and getMeta($model) on a shared base resource to get self links and per-resource meta consistently, then let subclasses only implement getAttributes and getRelationships. That keeps the boilerplate near zero and makes toArray() output predictable across every endpoint.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package