tobyz/json-api-server
A JSON:API server for Laravel that turns Eloquent models into spec-compliant APIs with resource classes, relationship includes, filtering, sorting, pagination, and validation. It streamlines building standardized JSON:API endpoints.
Architecture fit. tobyz/json-api-server remains a schema-driven JSON:API server built on the two-part Schema + Adapter pattern, but v1.0.0-rc.1 materially widens the abstraction surface. The package now exposes a first-class Endpoint layer (Show/Update aggregate separate *Resource and *Relationship endpoints), where endpoints and paginators can declare their own query parameters — used both for request validation and for OpenAPI schema generation. Alongside that: an Id field class for ID type constraints, client-generated IDs (writableOnCreate()), and custom validation; rich resource Link objects; overridable error classes plus JsonApi::errors() mapping; JSON:API profiles (parsed from Accept, activated on Content-Type); cursor-pagination profile support with Page::$rangeTruncated; and the async-processing recommendation (Create::async() → 202, Show::seeOther() → 303). Net effect: the package has moved from "spec-compliant serializer" to "near-complete JSON:API framework," which increases leverage but also increases the amount of framework we are adopting. The core value proposition — derive all spec behavior from two classes per resource — still holds.
Integration feasibility. Feasible, with the same shape of cost as before: one schema + one adapter per resource, plus (now) endpoint configuration for validation and schema generation, and an authorization hook. The per-resource cost rises modestly if we want the new leverage (query-parameter declarations, OpenAPI output, link definitions) rather than the minimum viable wiring. Two offsetting wins: query-parameter validation moves bad include/filter/sort requests from "slow query" to "400," and generated OpenAPI documentation reduces a class of work we would otherwise hand-maintain.
Technical risk. The dominant risk has shifted from "maturity" to pre-1.0 API churn — now empirically demonstrated. This single release renames or removes nearly every extension point a team would have touched on an earlier version: Resource::getId() → Resource::id(): Id, ErrorProvider::getJsonApiErrors() (plural) → getJsonApiError() (singular), the SingleError trait renamed to JsonApiError with a changed fluent API, Pagination\Concerns\BuildsUrls removed in favor of Context::currentUrl(), Extension\Atomic relocated, json_api_response() and Context::queryParam() removed, and custom ToOne deserializers now receiving a resolved model instead of the raw relationship object. Any PoC or schema code written against a pre-rc build must be rebased, not upgraded. Mitigating reading: batching all of this into a single rc.1 is the consolidation-before-freeze pattern, and the rc designation is a credible signal that a 1.0 with a stability commitment is close. Aggravating factors unchanged: single primary maintainer (the Flarum author), narrow adoption, no stated SLA. MIT license and a readable codebase keep the fork option viable.
Key questions for the package owner / community
filter query-parameter handling follow 1.0 or 1.1?Id field refactor change the adapter contract?Stack fit. Best fit remains a Laravel 9–11 (PHP 8.1+) application with Eloquent or a query builder, on a path we want spec-compliant. The new OpenAPI generation adds a reason to adopt for teams generating client SDKs or publishing partner-facing API docs. It is still a poor fit for teams wanting zero-config REST scaffolding or teams unwilling to absorb the JSON:API spec and the endpoint/profile/async surface.
Migration path. Unchanged in principle: parallel mounting, not replacement. Stand up the JSON:API server on /api/v2, implement resource-by-resource, cut consumers over per resource, keep /api/v1 as fallback. One addition: pin to v1.0.0-rc.1 exactly and do not track main during the build-out — the changelog demonstrates that main will move under us. If we already ran a PoC against a pre-rc build, treat that code as disposable and rebase it onto rc.1; do not attempt a line-by-line port of getId(), error providers, or pagination URL building.
Compatibility. Verify up front: PSR interface versions, PHP version ceiling (8.2+ is the safe assumption for the new feature set), whether the Eloquent adapter is first-party post-refactor, and whether the Id field class changes how adapters fetch records (it now defines ID schema, validation, and writability — writable IDs on create have data-layer implications: client-generated keys, collision handling, upsert semantics). Budget explicitly for the adapter; it remains the largest single integration cost.
Sequencing
1.0.0-rc.1, read every breaking change, confirm no dependency pulls a different constraint.*Relationship endpoints, which are distinct routes post-refactor and therefore a distinct authz surface.writableOnCreate() at all; default to server-generated IDs unless a client requirement forces otherwise.Accept profile URIs.Maintenance. Low code footprint, MIT, Composer-managed — but upgrade breakage is no longer a hypothetical, so treat version bumps as scheduled projects, not chores. For a 10-resource API, budget roughly one to two sprints for a breaking-change upgrade of this magnitude (schema base classes, error providers, pagination URL building, response helpers). Mitigation: pin exactly, mirror the repo internally, and fork-and-patch rather than chase upstream when a fix is urgent. Every model change still has a matching schema change — schemas are a versioned artifact and need migration-grade review.
Support. No SLA, no vendor; support is GitHub issues and source reading. The rc cadence suggests an actively developed package right now, which cuts both ways: responsive maintainer, but a moving API. Upstream responsiveness is coupled to Flarum's release rhythm. Document as a known risk with the fork path as the stated mitigation.
Scaling. Still serialization-bound and N+1-exposed, with better guardrails: query-parameter validation rejects undefined filters/includes/sorts before they reach the data layer, and cursor pagination now supports range truncation. Require: eager-loading discipline in adapters, hard caps on include depth and page size, cursor pagination for large collections, and edge caching (ETag/Last-Modified). Two new scaling considerations: OpenAPI schema generation is a build/startup-time cost worth measuring if it runs per request, and async processing introduces a real operational dependency — Create::async() returning 202 means queue workers, job retries, and a result-retrieval contract become part of the API's availability story, not just a backend detail.
Failure modes to monitor
include chains. Cap depth, monitor query counts per request.*Relationship endpoints explicit, separate routes — verify they inherit every policy check applied to the primary resource.seeOther() target that is honest about pending state.Endpoint::response(), saved(), and custom error overrides are powerful escape hatches; unbounded use re-creates the hand-rolled boilerplate this package exists to remove. Require review on any endpoint-level override.Ramp-up. 2–4 days per engineer to baseline productivity, but the surface has grown: JSON:API spec is still the main learning curve, and now profiles, async processing, and the endpoint/query-parameter/OpenAPI model add roughly another day for whoever owns the API layer. Budget a working session on the schema/adapter/endpoint triad plus one reference resource (schema + adapter + endpoints + generated OpenAPI fragment) as the template. Frontend teams still need ramp-up on sparse fieldsets, include, pagination links — and now profile negotiation, which is a client-visible contract decision, not a backend-only refactor.
How can I help you explore Laravel packages today?