enm/json-api-server-bundle
Symfony integration for enm/json-api-server that wires JSON:API handling into your project. Configure bundles, routing, and optional URL prefixes, register request handlers via service tags by resource type, and get built-in error handling for all standard JSON:API endpoints.
Reality check first: this is a Symfony bundle, not a Laravel package. There is no service provider, no config publish tag, and no Illuminate integration. A Laravel developer cannot composer require this into a Laravel app and get value — the bundle's entire payload is Symfony glue (bundle registration, YAML routing import, DI container tag collection, route-name-prefix-aware exception handling). Treat this package as (a) a reference implementation for how a JSON:API server is wired into a framework, or (b) an install target only if the target service is Symfony.
If you are actually on Symfony (the only path where this runs day-to-day):
composer require enm/json-api-server-bundleconfig/bundles.php:
Enm\Bundle\JsonApi\Server\EnmJsonApiServerBundle::class => ['all' => true],
config/routes.yaml:
json_api:
resource: "@EnmJsonApiServerBundle/Resources/config/routing.xml"
config/packages/enm_json_api_server.yaml with debug, url_prefix, and route_name_prefix (all optional).json_api_server.request_handler with a type matching the JSON:API resource type it serves.Where to look first: read the core library docs (enm/json-api-server on GitHub Pages) before the bundle README. The bundle just wires that library in; the request-handler contract, resource model, and error semantics live upstream.
First use case: a single read-only resource type end-to-end — GET /{type} and GET /{type}/{id} for, say, articles — to confirm routing, handler discovery, and error formatting all work before adding relationships or writes.
One handler per resource type, registered via DI tag.
App\RequestHandler\ArticleRequestHandler:
tags:
- { name: json_api_server.request_handler, type: 'articles' }
The type attribute is the JSON:API resource type string — it must match exactly what clients send in URLs and what appears in data.type. The bundle collects tagged services into a handler map keyed by that string.
Thin controller, fat handler. All HTTP verbs are dispatched through the single jsonApiAction (introduced in 2.0.0). Your handler is the natural place for query building, authorization checks, and mapping domain objects to JSON:API resources. Avoid putting business logic in controllers — there is exactly one controller and it's the bundle's.
Prefix everything or nothing, atomically. If you front the API with /api, set both url_prefix: '/api' and route_name_prefix: 'enm.json_api' to whatever your route YAML actually uses. These two settings are coupled: url_prefix is about URL matching, route_name_prefix is about the exception handler locating the generated link URLs correctly. They drift out of sync silently and produce wrong links.self values in error responses.
Lean on the automatic error handling. The bundle catches exceptions from request handlers and serializes them as JSON:API error objects. Standardize your domain exceptions (not-found → 404 error object, validation → 422 with source.pointer, etc.) rather than hand-building error responses. This is the single biggest day-to-day time saver and the part most worth studying even in a Laravel context.
Read patterns as a design reference for Laravel. If you land on "build our own JSON:API layer for Laravel," the shape this package encodes is reusable:
type).{type} endpoints).The parts you don't port are the Symfony-specific ones: bundle class, services YAML, ResourceProviderPass removal remnants, and the symfony/psr-http-message-bridge wiring behind the decorator.
Integration tip — vendor it. If you genuinely adopt this on Symfony, forked as a VCS/path repository, with an internal tag, not pulled fresh from Packagist. Put the JSON:API layer behind an internal interface (e.g. a DocumentBuilder or your own RequestHandlerInterface) so the vendor code is swappable when you migrate.
type mismatch is a silent failure. If the tag's type doesn't match the URL segment, the handler isn't found and you'll get an empty/404 rather than a useful message. Check the compiled container (debug:container --tag=json_api_server.request_handler) when a handler "doesn't fire."url_prefix / route_name_prefix drift. Both are needed when you prefix routes; forgetting route_name_prefix breaks the exception handler's ability to generate correct URLs in error documents. Verify by triggering an error (e.g. request a nonexistent id) and inspecting links.jsonApiAction. All behavior goes through the handler or through service decoration.pagination.limit config was removed in 3.0.0) are all upstream. Bundle docs are thin on purpose.json_api_server.request_handler tag for per-type behavior — cheap and safe; (2) a decorator on the bundle's controller/handler services — viable but private services limit what you can reach (most services were made private in 2.0.0); (3) fork and patch — the realistic long-term path.How can I help you explore Laravel packages today?