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

enm/json-api-server

Abstract server-side PHP implementation of the JSON:API specification, built on enm/json-api-common. Provides endpoints, request handling, and exception handling for building JSON:API-compliant APIs in Laravel or any PHP application.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer and immediately check what you actually got:

composer require enm/json-api-server
composer show enm/json-api-server enm/json-api-common

The last release is from October 2018, so before wiring anything into an existing app, try the install in a scratch Laravel project on your target PHP version. If Composer blocks it on PHP 8.x, stop there and evaluate a fork or a maintained Laravel-native JSON:API package rather than forcing it into production.

Where to look first

  1. Read the enm/json-api-common docs before the server docs. The server package builds on the common structures: Document, Resource, Relationship, Error, Link, and pagination. If you do not understand those, the server docs will be confusing.
  2. Then read, in order:
    • docs/json-api-server/index.md — Endpoints and Usage
    • docs/request-handler/index.md — Concept, Interface, Usage
    • docs/exception-handling/index.md
  3. The README is only an index. The real API reference is the rendered Common docs plus the source. Expect to read source when the docs are ambiguous.

First use case: one read-only resource

Start with GET /api/articles/{id}?include=author, not with full CRUD.

  1. Create an endpoint class for the articles type. The endpoint owns the type name and implements the package’s fetch hooks for a single resource and its relationships.
  2. Map an Eloquent Article to a package Resource. Cast the primary key to a string, map attributes explicitly, and build the author relationship with type/id linkage plus links.
  3. Register the endpoint on the request handler, construct the server, and bind it as a singleton in a service provider.
  4. Add one catch-all route and one controller. Do not create one Laravel controller per JSON:API resource.
// routes/api.php — put this last, or scope it under a dedicated prefix.
Route::any('/{path}', JsonApiController::class)->where('path', '.*');

// app/Http/Controllers/JsonApiController.php
public function __invoke(Request $request, JsonApiServer $server)
{
    $psrRequest = $this->toPackageRequest($request); // PSR-7 bridge if required
    $psrResponse = $server->handleRequest($psrRequest);

    return response($psrResponse->getBody()->getContents(), $psrResponse->getStatusCode())
        ->withHeaders($this->filterHeaders($psrResponse->getHeaders()))
        ->header('Content-Type', 'application/vnd.api+json');
}

Test it with the correct media type:

curl -H 'Accept: application/vnd.api+json' \
  'http://localhost/api/articles/1?include=author'

If that works, add collection fetch, then relationship endpoints, then writes.

Implementation Patterns

One endpoint per type, dispatch by type. The request handler should resolve the endpoint from the JSON:API resource type. Keep Laravel routing thin: one catch-all controller, all JSON:API routing inside the package. This is the opposite of Laravel’s “route per controller action” habit.

Separate transport, endpoint logic, and persistence. A workable layering:

  • Controller: converts Laravel request/response to/from the package’s request/response types, sets the JSON:API content type, and nothing else.
  • Endpoint: parses include, fields, sort, filter, and page, authorizes the action, validates input, calls a repository/query service, and returns package Resource/Document objects.
  • Mapper: converts Eloquent models to package resources and relationships. Keep this out of models and controllers.
  • Repository/query service: builds Eloquent queries, applies eager loading and filters, returns models or paginators.

This keeps package classes out of your domain code and makes a future replacement feasible.

Read patterns

  • Single resource: fetch by type and ID, authorize, map to Resource, attach requested relationships.
  • Collection: parse pagination, sorting, filters, sparse fieldsets, and includes; apply allowlists; return resources plus pagination links/meta.
  • Relationship endpoints: implement both forms:
    • /articles/1/relationships/author — resource linkage only (data with type/id).
    • /articles/1/author — the related resource document.
  • Do not conflate relationship linkage with the related resource. They are different response shapes.

Write patterns

  • Validate data.attributes with Laravel’s validator or a FormRequest before constructing resources. Convert validation failures into JSON:API Error objects with source.pointer such as /data/attributes/title.
  • On create, reject client-supplied id, enforce data.type, wrap persistence in a transaction, and return 201 with a Location header.
  • On update, apply only provided attributes. JSON:API partial updates are not “replace the whole model”.
  • On delete, return 204 with no body.
  • Use policies or gates inside the endpoint. Authorization must also apply when traversing relationships.

Query building

  • filter[...] must be an allowlist mapped to columns/operators. Never pass raw query input into where.
  • sort is comma-separated; -created means descending. Validate against allowed fields and return 400 for unsupported sorts.
  • page[number] and page[size] map to forPage. Cap page size. Generate pagination links with the current query string preserved.
  • Parse include and eager load with with() before mapping. Enforce an allowlist and a maximum include depth.

Links and URL generation. The package cannot know your Laravel host, proxy configuration, or route names. Generate absolute URLs with url()/route() inside endpoints, or inject a URL generator. Set self, related, and relationship links deliberately. Behind a load balancer, configure Laravel Trusted Proxies first.

Error handling. Centralize it. Throw domain exceptions from endpoints/repositories, then map them to JSON:API Error documents with status, title, detail, code, and source. Add a branch in Laravel’s exception handler for API routes so 401, 403, 404, 422, and 500 responses are also JSON:API-shaped. Middleware auth failures are a common leak: they return Laravel’s default JSON unless you intercept them.

Container and testing. Bind the server/handler as a singleton and resolve endpoints from the container so constructor injection works. Write feature tests through the catch-all route and assert:

  • Content-Type is application/vnd.api+json.
  • data.type and data.id exist and id is a string.
  • included contains no duplicate type/id pairs.
  • Validation errors are in errors with the correct source.pointer.
  • 204 delete responses have no body.

Add a JSON:API schema validator to CI if the API is public.

Caching. Cache keys must include the full query string (include, fields, sort, page, filter), the Accept header, and the authenticated user context. Send Vary: Accept.

Gotchas and Tips

  • It is archived and effectively unmaintained. Last release: 2018-10-29. Expect PHP 8 compatibility problems, outdated dependency constraints, and no security patches. Pin the exact version, commit the lock file, and treat it as vendored code. If you must use it, isolate it behind your controller/endpoint layer so it can be replaced.
  • Tiny ecosystem. 12 stars and 0 dependents means very few answers online. Budget time to read the source. When docs and code disagree, code wins.
  • It is abstract, not a Laravel integration. You get JSON:API document structures and dispatch concepts, not Eloquent mapping, routing, auth, validation, pagination adapters, or URL generation. The glue is your responsibility, and it is substantial.
  • Do not install the Symfony bundle. The same family has a Symfony integration; it is not for Laravel. Wire the server manually.
  • PSR-7 boundary. If the package expects PSR-7, Laravel requests are not PSR-7. Use a bridge such as symfony/psr-http-message-bridge with nyholm/psr7 or guzzlehttp/psr7. Preserve the raw request body with $request->getContent() for writes. When converting back, copy status, headers, and body; force application/vnd.api+json and do not double-encode an already serialized body.
  • JSON:API is strict about IDs. Resource id values must be strings. (string) $model->getKey() everywhere. Integer IDs will fail schema validation and confuse clients.
  • Relationship shapes. Missing to-one relationship: data => null. Empty to-many: data => []. Do not omit the key when linkage is requested. included must be unique by type + id; do not nest duplicate resources.
  • Sparse fieldsets interact with includes. fields[articles] applies to the primary resource, while fields[people] applies to included authors. If you emit attributes after mapping, filter them by the requested fieldset for both primary and included resources.
  • Content negotiation. Clients should send Accept: application/vnd.api+json. Writes with the wrong Content-Type should get 415; unacceptable Accept should get 406. Laravel will not do this for you. Be pragmatic about lenient reads, but be strict on writes.
  • Status codes matter. 201 + Location for create, 200 for update, 204 with no body for delete, 409 for conflicts, 422 for validation with pointers, 401/403 for auth. A JSON:API client will not treat a 200 delete with a body as correct.
  • N+1 queries and include abuse. The main performance risk is include. Eager load requested relationships, allowlist include paths, and cap depth and count. Relationship endpoints can otherwise trigger expensive traversals.
  • Do not mix with Eloquent API Resources. JsonResource produces a different envelope and will fight this package. Pick one serialization approach.
  • Route catch-all ordering. Put the JSON:API catch-all last or under a dedicated prefix so it does not swallow other API routes. Handle trailing slashes consistently; Laravel and JSON:API clients can disagree.
  • Pagination links behind proxies. Use Laravel’s trusted proxy configuration and url()->current() rather than hardcoding hosts. Preserve existing query parameters in self/first/last/next/prev links.
  • Debugging dispatch failures. If you get unexpected 404/405, dump the parsed package request (type, id, relationship name, includes) and verify endpoint registration and type matching. If a 500 lacks detail, temporarily bypass Laravel’s exception handler and inspect the package exception; log the package request object.
  • Extension points are limited by abandonment. You can subclass endpoints, the request handler, and the exception handler, and use the Common package’s Resource, Document, and Error classes. Since upstream is inactive, prefer composition and a local fork over waiting for upstream changes.
  • Have an exit plan. Keep JSON:API behavior behind your own controller/endpoints so migrating to a maintained Laravel-native JSON:API package or a hand-rolled serializer is a contained change rather than a rewrite.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
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