haddowg/json-api-symfony
A Symfony bundle for building JSON:API-compliant APIs, handling resource serialization, relationships, and spec-compliant responses with minimal configuration.
Build vs. buy for spec-compliant API surface. v1.0.0 represents the moment a hand-rolled JSON:API layer becomes hard to justify: the release consolidates a query engine (filter/sort/pagination incl. cursor keyset on collections, related collections, and pivot/linkage endpoints), adds build-time failure on misconfigured polymorphic/Doctrine-column/pivot types, and closes the spec-compliance gaps (sparse fieldsets, strict query params, related-type factories, host-absolute links). Each of those is a multi-sprint "we'll do it properly later" backlog item for an internal team.
Roadmap: async and non-CRUD workflows are now first-class. The async write seam (202 Accepted / 303 See Other) plus custom (non-CRUD) actions with declared meta-only output means the API layer can now model real product operations — imports, exports, bulk reprocessing, state transitions — without inventing a parallel RPC endpoint style. This directly de-risks any roadmap item that requires long-running or side-effecting operations.
Roadmap: partner/public API and SDK strategy. Bridging resource/operation/relation description overrides into OpenAPI, advertising the id pattern and the Atomic Operations endpoint, and documenting operations as secured-or-public via a security boolean give us a machine-readable contract as a build artifact. That unlocks generated client SDKs, partner sandbox docs, and contract-diff review in CI — a decision that normally needs a separate "API governance" investment. Custom-encoded and policy-sourced resource ids extend this to ID strategy: we can control identifier shape and source without forking serialization.
Authorization and data-exposure decisions. Independent relationship authorization, read-security expressions gating relationship and related endpoints, collection reads gated by a separate securityList key, request-aware visibility/writability predicates, and now write-only attributes (writeable but never serialized out) plus readOnly shorthand mean we can ship nuanced tenancy and field-level access policies declaratively rather than by custom controller surgery. This is the difference between "we can do per-tenant APIs" and "we can do per-tenant APIs without a bespoke audit each time."
API lifecycle and deprecation management. Cache headers and deprecation headers are now part of the framework surface, not bespoke middleware. Combined with strict query parameters and the Validator bridge (including When and date constraints), we get a defensible story for versioning, sunsetting endpoints, and validating writes — all things normally specced as separate platform work.
Integration risk as a decision input. The exception-to-error mapping seam, typed constraint-translator extension, DataProvider SPI base classes, Validator bridge, and the readOnly resource shorthand reduce the coupling between our domain code and the package. That lowers the cost of the eventual upgrade path and is a real factor in the build-vs-buy spreadsheet.
Migration planning. v1.0.0 is a large breaking-change boundary (linkage method renames, lazy-by-default linkage data, opt-in counting and pagination-as-single-source-of-truth, mandatory related-type relationship factory, DataProviderInterface additions, DoctrineExtensionInterface signature change, removed zero-config pivot filter, and belongsToMany pivot writes moving into meta.pivot). This is a discrete, one-time migration project to scope — not an incremental upgrade — and it should be scheduled as such against the 1.0 release rather than discovered mid-sprint.
Platform caveat affecting the decision. Despite being a PHP package, this is a Symfony-oriented implementation (issue tracker and extension naming are Symfony-side). If our target is Laravel, adopting it is a bridging project, not a composer require. That belongs in the build-vs-buy math, and it is the single largest unknown in this evaluation.
Adopt when several of the following hold:
Look elsewhere when:
To executives: This package is the difference between building an API platform and building an API. v1.0.0 is the stabilization milestone: it closes the spec-compliance gaps, generates our OpenAPI contract automatically, enforces API correctness at build time, and makes async operations and custom actions first-class. It also brings API lifecycle fundamentals — deprecation headers, cache headers, write-only attributes, strict query params — into the framework instead of bespoke middleware. Concretely, it converts several recurring roadmap asks — partner API access, generated SDKs, long-running operations, tenant-scoped field visibility, and endpoint sunsetting — from bespoke projects into configuration. The cost is a one-time migration from any pre-1.0 usage, plus an integration assessment if we are standardizing on Laravel rather than Symfony. Recommendation: fund a short spike to quantify the Laravel bridging cost, then decide; treat the 1.0 migration as a scheduled workstream, not incidental maintenance.
To engineering: v1.0.0 is a breaking-change boundary and should be treated as a discrete migration with its own ticket and test plan. The high-impact items: relationship linkage methods renamed to data; linkage data is lazy by default; counting and pagination are now opt-in with pagination as the single source of truth; the related-type relationship factory is mandatory; DataProviderInterface gains relatedToOneMatches() / relatedToOneMatchesBatch(); DoctrineExtensionInterface::apply() now takes an ExtensionContext; the zero-config filter[position] pivot filter is removed in favor of a pivot.-prefixed declaration; belongsToMany pivot writes now read from meta.pivot (symmetric with reads) and pivot meta is rendered on primary-resource linkage; unknown sparse-fieldset members are rejected under the strict gate; read-security now cascades to relationship and related endpoints, so existing authorization tests will need review. Upside for us: build-time validation of polymorphic/pivot/column misconfiguration, an exception-to-error mapping seam, DataProvider SPI base classes, cursor pagination on related and pivot endpoints, an async write seam (202/303), custom (non-CRUD) actions with declared meta-only output, the Atomic Operations extension, and a completed Validator bridge (When, date constraints). Please produce a diff-level migration estimate against our current usage before we commit a release date.
How can I help you explore Laravel packages today?