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

tobscure/json-api-server

A PHP library for building JSON API-compliant APIs, providing serializers, resource transformers, and relationship handling. Framework-agnostic and easy to integrate into Laravel or any PHP project.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a JSON:API server implementation for Laravel: you describe your resources once (fields, IDs, relationships, links, query parameters, endpoints) and the package handles request parsing, validation, serialization, errors, pagination, and OpenAPI schema generation from that description.

Minimal steps:

  1. composer require the package and publish its config: php artisan vendor:publish. The config file lists your registered servers; each server names the schemas and endpoints it exposes and its base URI.
  2. Register a Schema for one model. The new v1 shape puts the ID in its own id() method returning an Id field:
class PostSchema extends Schema
{
    public function id(): Id
    {
        return Id::make()
            ->type('integer')          // schema/validation constraint
            ->required();              // server-generated
    }

    public function fields(): array
    {
        return [
            Str::make('title')->required()->sortable(),
            Str::make('body')->nullable(),
            BelongsTo::make('author')->required(),
        ];
    }

    public function links(): array
    {
        return [
            Link::make('self')->url(fn ($post) => route('posts.show', $post)),
        ];
    }
}
  1. Wire up endpoints. Show and Update now aggregate separate resource and relationship endpoints, so GET /posts/1 and GET /posts/1/relationships/author are routed to different handlers behind the same endpoint declaration.
  2. Hit the endpoint with Accept: application/vnd.api+json. From this moment, the generated OpenAPI document is your best debugging tool — it reflects ID constraints, query parameters, and endpoint headers as you declare them.

Where to look first, in order: src/Schema (fields and types), src/Endpoint (HTTP semantics and hooks), src/Context (everything the request knows and everything the response needs), then the generated OpenAPI schema.

First real use case: a read-only Index + Show for one resource, including a filter query parameter. Declare the parameter on the endpoint (not in a controller) and it appears in validation and in the schema for free.

Implementation Patterns

Endpoints own query parameters. Rather than validating in controllers, endpoints and pagination implementations declare the parameters they accept. Validation and OpenAPI generation both derive from the same declaration, so an undocumented parameter is an unaccepted parameter:

class Index extends Endpoint
{
    public function parameters(): array
    {
        return [
            Filter::make('status')->using(...),
            ...parent::parameters(),
        ];
    }
}

Inside handlers, read them with Context::parameter('status').

Build responses through Context, never the removed helper. json_api_response() is gone; use Context::createResponse(), which includes the jsonapi object automatically. Add document-level meta via JsonApi::meta(). For URLs — including the previously hand-rolled BuildsUrls logic — use Context::currentUrl(['page[size]' => 50]).

Override responses per endpoint, not globally. Endpoint::response() registers a custom callback, Endpoint::headers() adds typed headers via the Header schema class, and Endpoint::schema() customizes the generated schema. Create::saved() / Update::saved() are the right place for post-persist work (dispatch a job, attach a tag) because they run after the model and all fields are saved but before serialization. Resolved input is available on Context::$data there.

Async work follows the recommendation, not a custom 202. Create::async() returns 202 Accepted with the right semantics; Show::seeOther() returns 303 See Other for "your resource is ready, go here". Prefer these over hand-building responses so clients can rely on standard behaviour.

Error customization is per exception class. Every error now has a concrete exception class. Register copy/format overrides once:

JsonApi::errors([
    RecordNotFoundException::class => ['detail' => 'No :resourceType with id :id.'],
]);

:key placeholders are substituted from the exception. Use Exception\JsonApiErrorsException to return several errors at once (validation is the obvious case); each member becomes a JSON:API error object.

Custom errors are single errors now. An ErrorProvider (or exception) implements getJsonApiError(): array; if your code previously returned a list of errors, either split into one exception per error and wrap them, or build a JsonApiErrorsException. The Exception\Concerns\JsonApiError trait gives you the fluent, null-tolerant builders: source(), meta(), links().

Use JSON:API profiles for pagination-aware clients. Cursor pagination auto-activates https://jsonapi.org/profiles/ethanresnick/cursor-pagination. Elsewhere, gate behaviour on Context::profileRequested($uri), inspect requestedProfiles(), and call activateProfile($uri) so the response Content-Type advertises what you actually implemented.

Type system for nested nullability. nullable() now exists on Type classes, so array items and object properties can be nullable independently of the field. Reach for Obj, Any, AnyOf, AllOf, OneOf, and Not when a field is polymorphic or a union of scalars — these replace ad-hoc "any" fields that used to serialize inconsistently and generate no schema.

Relationships. linkageMeta() adds meta to resource identifier objects in linkage (useful for paginating huge to-many linkage). Custom deserializers on ToOne now receive the resolved model, matching ToMany — a single signature mental model for both.

Generic serializers/exporters. Field::location() returns attributes, relationships, or root-level for id, so code that walks fields (CSV exports, search indexers, audit logs) can order or filter by document shape rather than hardcoding field names.

Gotchas and Tips

Migrating from pre-1.0 — the five changes that touch real code:

  • Resource::getId(object $model, Context $context): string is gone. Move ID logic into id(): Id with getter callbacks and ->type(...). Anywhere you previously branched on ID format, branch on the Id field instead.
  • ErrorProvider::getJsonApiErrors() → getJsonApiError() (singular). Multi-error exceptions must be wrapped in JsonApiErrorsException.
  • Exception\Concerns\SingleError → Exception\Concerns\JsonApiError, and the setters are now fluent and non-nullable-arg: setSource(?array) → source(array). Calls like ->setMeta(null) must simply be deleted.
  • json_api_response() → Context::createResponse(); Context::queryParam() → Context::parameter() (defined parameters only) or Context::$request->getQueryParameters() (raw/undefined).
  • Extension\Atomic moved to Extension\Atomic\Atomic, and Pagination\Concerns\BuildsUrls is deleted in favour of Context::currentUrl().

Context::parameter() throws or errors on undefined parameters. This is intentional — it forces you to declare parameters on the endpoint. If you genuinely need an arbitrary query string, go to Context::$request->getQueryParameters(); don't declare a fake parameter just to silence it.

Writable IDs are opt-in and affect validation. Client-generated IDs need Id::make()->writableOnCreate() plus your own rules; otherwise a client-supplied id on create is rejected (correctly). The generated OpenAPI now reflects ID constraints and omits redundant properties, so if your client SDK generation changes after upgrading, check the Id field first.

Schema generation has its own context. SchemaContext is what schema-related hooks receive — don't reach for Context (request state) inside Endpoint::schema() or Endpoint::headers(); it may not represent the same thing, and it's the usual cause of "works at runtime, wrong in OpenAPI" bugs.

saved() runs before serialization, not after the response. Mutating fields there is safe; assuming the document is built is not. Similarly, Endpoint::response() callbacks run with a serialized payload — mutate the payload, not the model.

meta() on the jsonapi object is document-scoped. If meta needs to vary per resource, use resource-level links()/meta instead; putting instance data in JsonApi::meta() leaks it across every resource in the document, including compound documents.

Cursor pagination truncation is reported, not hidden. Page::$rangeTruncated (and the auto-activated profile) lets clients detect truncated ranges. If you build custom cursor paginators, set it — silent truncation is the classic cause of "missing records" bug reports.

Debugging order: (1) is the query parameter declared on the endpoint? (2) does the generated OpenAPI match what you expect — ID constraints, links, headers? (3) is the exception a concrete class you can override with JsonApi::errors()? (4) does the Accept header request a profile that changes behaviour? Most "weird response" issues resolve at step 1 or 4.

Performance: this release adds serialization optimizations, but they don't rescue N+1 includes. Eager-load relationships from the schema (with()-style declarations) rather than per-field lazy access; Field::location() and schema-driven loading make bulk serialization significantly cheaper than hand-written transformers.

Pin the RC in CI and re-run your OpenAPI diff. With endpoints, query parameters, and schema generation all refactored, the generated document is the highest-signal check that an upgrade didn't change your public contract.

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.
skyraptor/chatter
edlugz/sasapay
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