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

haddowg/json-api-symfony

A Symfony bundle for building JSON:API-compliant APIs, handling resource serialization, relationships, and spec-compliant responses with minimal configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

v1.0.0 is the first stable surface of this JSON:API server implementation. Note up front: this is a Symfony/Doctrine bundle, not a Laravel package — there is no service provider, no Eloquent adapter, and the validation bridge targets Symfony Validator. If you are evaluating it from a Laravel codebase, treat it as a reference implementation and a contract source (the JSON:API spec profiles it implements), and plan on writing a DataProviderInterface adapter over Eloquent before you get any value.

Minimal path to a first request:

  1. Install via Composer and register the bundle plus the extensions you actually need (relationship queries, atomic operations, OpenAPI are all opt-in).
  2. Define one resource for one type. Start from the readOnly shorthand — it wires up the read capability in a single declaration and is the cheapest way to see the machinery work end to end.
  3. Wire a data provider. Ship-with-Doctrine works out of the box; anything else means implementing DataProviderInterface, including the newer batch methods (relatedToOneMatches(), relatedToOneMatchesBatch()).
  4. GET /{type} and GET /{type}/{id}. Then diff the generated OpenAPI document against what you expected.

Where to look first:

  • The example app. The composite-attribute showcase resource (PR #108) is the most representative single file in the repo — it demonstrates attribute composition, which is the feature most likely to reshape your resource layer.
  • The generated OpenAPI document. v1.0.0 treats OpenAPI as a first-class output: descriptions from resources, operations, and relations are bridged through (#68), the Atomic Operations endpoint is advertised (#56), the id pattern is published (#64), and security booleans document secured vs. public operations (#74). Read it before you read the source.
  • The build errors. This release deliberately fails the build on misconfiguration (polymorphic relations, Doctrine column mismatches, pivot config, unservable routed types — #62, #63). They are the fastest documentation you have.

First real use case: expose an existing read-mostly aggregate as a JSON:API collection with sparse fieldsets, one included relationship, and cursor pagination. That exercises the query engine, the lazy linkage layer, and pagination-as-source-of-truth in one go.

Implementation Patterns

Compose types from capabilities, not inheritance. The type declaration in 1.0 is assembled from independent optional capabilities (read, write, count, relationship-queries, custom actions). Prefer enabling capabilities per type over forking a base resource — the readOnly shorthand (#65) and write-only attributes exist precisely so a type can advertise asymmetry without a separate class.

Relationships are typed and windowed. The mandatory related-type relationship factory (#61) means every relationship names its related type explicitly; there is no implicit inference. Attributes use the flattened on() form (#50). Windowed includes are handled by the framework — you should not be hand-limiting included relations.

Linkage data is lazy by default. data() replaced the old linkage methods (#36) and does not resolve until the document is rendered (#39). Reach for eager resolution only when you genuinely need the linkage inside your own code; otherwise you are paying for includes nobody asked for.

Pagination is the single source of truth. v1.0.0 makes pagination opt-in and canonical (#40). Both offset and cursor/keyset pagination are supported — including on related-collection endpoints (#110) and on pivot-related and linkage endpoints (#111). Counting is not free: totals are emitted only when pagination counts (#41), and ?withCount is gated behind the Relationship Counts profile (#35). Declare countable relations explicitly rather than assuming counts appear.

to-many relationship endpoints are full query surfaces. GET /{type}/{id}/{rel} supports filter, sort, and pagination (#71) via the consolidated query engine. Pivot filters use the pivot.-prefixed declaration form; operators on pivot filters are supported. Do not build bespoke "filter a relationship" endpoints — this already covers it, and doing it yourself forfeits the OpenAPI projection of every sortable field (#62).

Security is request-aware and per-endpoint. Visibility, writability, and relationship-authorization predicates execute against the current request (#48). Collection reads are gated by a distinct securityList key (#75) — separate from item read. Critically, a resource's read-security expression now also gates its relationship and related-read endpoints (#47/#48 breaking note), and relationships can be authorized independently of their parent (#76). Model this as: item read, list read, relationship read, relationship write are four separate decisions.

Custom actions are first-class. Non-CRUD actions (#42) let you expose domain operations as JSON:API endpoints rather than smuggling them into PATCH. In 1.0 you must declare the action's meta-only output explicitly (#85) — the response shape is part of the contract, so it also lands in OpenAPI.

Writes can be async. The async write seam (#104) lets a create/update return 202 Accepted with a pointer to poll, or 303 See Other once complete. Use it for anything that triggers queues; it keeps the JSON:API document shape valid while the work finishes.

Atomic Operations (#55/#56) give you a single /operations endpoint with all-or-nothing semantics — the right tool for multi-resource client-side saves that must not half-apply.

Errors go through a mapping seam. Rather than catching and reshaping exceptions in every action, register exception→error-object mappings (#38). This keeps errors[] shapes consistent and lets domain exceptions stay domain exceptions.

Also available: cache and Deprecation headers plus strict query-parameter rejection; custom-encoded and policy-sourced resource IDs (#0cdcdb7); and a Validator bridge covering When and date constraints (#10).

Gotchas and Tips

  • Upgrading from a pre-1.0 alpha is a rewrite, not a bump. This is a large breaking release: linkage methods renamed to data(), on() attributes flattened, the zero-config filter[position] pivot-equality filter removed in favour of pivot.-prefixed declarations, and the Custom bridge dropped in favour of a typed constraint-translator extension (#13). Budget accordingly.
  • Custom data providers must implement both new batch methods. relatedToOneMatches() and relatedToOneMatchesBatch() are mandatory on DataProviderInterface. A partial implementation fails at runtime, not at compile time — implement the batch variant as a real batched query, or you have reintroduced N+1 behind a compliant-looking interface.
  • DoctrineExtensionInterface::apply() now takes an ExtensionContext. Loose-argument extensions silently break. If you are porting the Doctrine extension pattern to an Eloquent equivalent, mirror the context object — it is what carries the request-scoped information the predicates need.
  • Links are request-host-absolute by default (#57). Behind a load balancer or Laravel's TrustProxies equivalent, you will emit http://internal-host/... unless trusted hosts/proxies are configured. This is the most common "works locally, wrong in staging" bug in this class of package.
  • Pivot writes now read from meta.pivot (#80), symmetric with reads (#79). Client code that sent pivot attributes as top-level relationship members will silently no-op. Pivot meta is rendered on primary-resource document linkage — check your expected fixtures.
  • Unknown sparse-fieldset members and unknown query parameters are rejected under the strict gate (#49). That is correct behavior and it will break sloppy clients. Turn it on early in a project, not late.
  • Don't enable counting speculatively. Counts only materialize when pagination counts; ?withCount requires the Relationship Counts profile. If totals are missing, the cause is almost always a profile that isn't enabled or a relation not declared countable — not a bug.
  • Lazy linkage hides N+1s, it does not prevent them. A resource that calls data() inside an attribute computation defeats the lazy default. Keep attribute computation pure; let the serializer own linkage.
  • Build-time failures are a feature — read them literally. Polymorphic relations, Doctrine-column mismatches, pivot misconfiguration, and routed-but-unservable types all abort the build. Resist the temptation to catch-and-warn; those checks are the only thing standing between you and a 500 on a client's first request.
  • From Laravel specifically: the pagination model here is JSON:API pagination (offset links or keyset cursors), not Laravel's LengthAwarePaginator. Cursor identity is opaque and must round-trip exactly. Likewise, validation is Symfony Validator constraint-based — do not assume Laravel FormRequest rules transfer; you will be re-declaring constraints, and the When/date coverage in the bridge is where the parity work lands.
  • Extension points worth knowing: the exception-to-error mapping seam, the typed constraint-translator extension, the data-layer SPI base classes (#64), and the capability composition layer. Those four cover most "the package doesn't do X" situations without forking.
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.
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
noith/settings
kda/laravel-package