haddowg/json-api — a server-side JSON:API 1.1 library for PHP¶
haddowg/json-api is a modern, server-side JSON:API 1.1
library for PHP 8.3, 8.4 and 8.5. It is framework- and storage-agnostic: you bring
a domain model and a PSR-7/PSR-17 implementation, declare your resource types, and
the library handles negotiation, body parsing, sparse fieldsets, includes, error
rendering, and encoding. You stay in plain PHP value objects throughout — there is
no router and no ORM baked in.
What it does¶
The library exists to do four things well:
- Verifiable JSON:API 1.1 compliance. The spec's MUST/SHOULD rules are tracked, and the example app's test suite asserts every response is spec-compliant.
- First-class server-side profiles. Profiles are a built-in extension point, not an afterthought — you register them on the server and they participate in content negotiation.
- A shipped PSR-15 middleware suite for the standard request lifecycle — content negotiation, body parsing, error handling — so the common path is wired for you. See middleware.
- A stable, production-suitable foundation. Immutable value objects, a typed exception hierarchy, and a single field declaration that drives both serialization and hydration.
Scope boundaries¶
This is a server-side library. The following are deliberately out of scope:
- Client-side consumption — building or parsing requests as an API consumer.
- Framework integration. The library is framework-agnostic; idiomatic Symfony
integration (DI, routing, the request lifecycle) lives in a separate bundle,
haddowg/json-api-symfony. - Migration tooling — there are no codegen or schema-migration helpers.
Install¶
The library depends on a PSR-7 HTTP message
implementation and a PSR-17 factory; it does
not ship one. The examples and tests use nyholm/psr7:
A taste¶
You declare a resource type by subclassing AbstractResource, giving it a $type,
and listing its fields. One field declaration drives both directions — read and
write:
use haddowg\JsonApi\Resource\AbstractResource;
use haddowg\JsonApi\Resource\Field\Id;
use haddowg\JsonApi\Resource\Field\Str;
final class AlbumResource extends AbstractResource
{
public static string $type = 'albums';
public function fields(): array
{
return [
Id::make(),
Str::make('title')->required()->maxLength(200)->sortable(),
// …
];
}
}
You then register the resource on an immutable Server — the per-version
configuration root — and wire your PSR-17 factories:
use haddowg\JsonApi\Server\Server;
use Nyholm\Psr7\Factory\Psr17Factory;
$psr17 = new Psr17Factory();
$server = Server::make()
->withBaseUri('https://music.example')
->withPsr17($psr17, $psr17)
->register(AlbumResource::class);
This is a sketch, not a copy-paste runnable program — a real service also wires a middleware chain, a router, and an operation handler. The getting-started walkthrough builds the whole thing end to end against the music-catalog example app, with every snippet lifted from a CI-run test.
Design philosophy¶
A few principles run through the whole library:
- Immutable value objects. The
Server, responses, and parsed request data are readonly; everywith…()method returns a new instance. - Enums and typed exceptions. Fixed vocabularies (relationship modes, comparisons, media-type parameters) are enums; failures are a typed exception hierarchy carrying their own HTTP status.
- One declaration, both directions. A field listed in
fields()describes how a member serializes and how it hydrates — you do not maintain two parallel maps. - First-class profiles. Profiles are a registered server concern, negotiated like any other media-type parameter.
Credits and licence¶
This package derives substantial portions from
woohoolabs/yin; its original authors'
copyright is preserved alongside this derivative work. The fluent field and
constraint layer is inspired by Laravel JSON:API. It is released under the MIT
licence with a dual copyright (Gregory Haddow; Woohoo Labs and contributors) — see
LICENSE.
Where to go next¶
Start with the getting-started walkthrough, then reach for the reference pages as you need them:
- Getting started — getting-started (the end-to-end walkthrough), concepts (the document model and vocabulary), architecture (how a request flows through the library).
- Defining resources — resources, fields, field types, ids, relations, constraints.
- Querying — filters (including
related-attribute traversal with
WhereThrough), sorts, pagination (page/offset/cursor strategies, with a default page-size cap), sparse fieldsets and includes. - Serialization & hydration control — serializers, hydrators, capability composition.
- API documentation — OpenAPI generation (projecting your server's metadata into an OpenAPI 3.1 document).
- Request/response lifecycle — server (including the
servingpre-operation hook), operations, related endpoints (with countable relations and?withCount— see relations), relationship mutation, responses, content negotiation, errors and exceptions, middleware. - Cross-cutting — adapters, schema validation, profiles, links and meta, security, testing.