Installation:
composer require paknahad/jsonapi-bundle
Add to config/bundles.php:
Paknahad\JsonApiBundle\JsonApiBundle::class => ['all' => true],
Generate a JSON:API-compliant Entity:
Use Symfony MakerBundle to scaffold an entity (e.g., Book):
bin/console make:entity Book
Ensure your entity uses Doctrine ORM annotations (e.g., @ORM\Entity).
First API Endpoint:
Create a controller extending JsonApiController (provided by the bundle):
use Paknahad\JsonApiBundle\Controller\JsonApiController;
class BookController extends JsonApiController
{
public function index(): Response
{
return $this->jsonApiResponse($this->getDoctrine()->getRepository(Book::class)->findAll());
}
}
Route it in config/routes.yaml:
api_books:
path: /api/books
controller: App\Controller\BookController::index
methods: GET
Test the Endpoint:
Visit /api/books to see JSON:API-formatted output (e.g., data, links, meta).
JsonApiController for standardized responses.config/packages/jsonapi.yaml (if auto-generated) for global settings.Fetch and Serialize a Resource:
// src/Controller/BookController.php
use Paknahad\JsonApiBundle\Controller\JsonApiController;
class BookController extends JsonApiController
{
public function show(Book $book): Response
{
return $this->jsonApiResponse($book); // Auto-serializes to JSON:API format
}
}
Route:
api_book_show:
path: /api/books/{id}
controller: App\Controller\BookController::show
methods: GET
Output:
{
"data": {
"type": "books",
"id": "1",
"attributes": {
"title": "Laravel JSON:API",
"published_at": "2023-01-01"
}
}
}
Entity Serialization:
@ORM\Column, @ORM\ManyToOne).woohoolabs/yin to auto-convert entities to JSON:API.class Book
{
/**
* @ORM\ManyToOne(targetEntity="Author")
*/
private $author;
}
Output includes nested author under relationships.Customizing Responses:
jsonApiResponse() in your controller:
return $this->jsonApiResponse($data, 200, [
'meta' => ['custom' => 'value'],
'links' => ['self' => '/api/books/1']
]);
JsonApiResponse class directly for granular control.Pagination:
Paginator (Symfony) and pass to jsonApiResponse:
$paginator = $this->getDoctrine()->getRepository(Book::class)->findAll();
$paginator->setItemOptions(['resource' => 'books']);
return $this->jsonApiResponse($paginator);
links for pagination (e.g., first, last, next).Relationships:
@ORM\ManyToOne).return $this->jsonApiResponse($book, null, [
'include' => ['author'] // Eager-loads author data
]);
Symfony MakerBundle:
make:entity to scaffold JSON:API-ready entities.bin/console make:entity Book --fields="title:string author:ManyToOne:Author"
API Platform Integration:
jsonapi-bundle as the serializer:
# config/packages/api_platform.yaml
api_platform:
formats:
jsonapi: ['application/vnd.api+json']
serialization_groups: [jsonapi]
Validation:
use Symfony\Component\Validator\Constraints as Assert;
class Book
{
/**
* @Assert\NotBlank
*/
private $title;
}
errors field.Testing:
$client = static::createClient();
$client->request('GET', '/api/books');
$response = $client->getResponse();
$this->assertJsonApiResponse($response, 200);
assertJsonApiAttribute()).Dynamic Resource Types:
getResourceType() in your controller for dynamic types:
public function getResourceType(): string
{
return 'dynamic_books';
}
Custom Serializers:
JsonApiSerializer to handle custom logic:
use Paknahad\JsonApiBundle\Serializer\JsonApiSerializer;
class CustomSerializer extends JsonApiSerializer
{
protected function serializeAttribute($attribute, $value)
{
if ($attribute === 'title') {
return strtoupper($value);
}
return parent::serializeAttribute($attribute, $value);
}
}
services:
Paknahad\JsonApiBundle\Serializer\JsonApiSerializer:
class: App\Serializer\CustomSerializer
GraphQL-like Queries:
// GET /api/books?fields[books]=title&include=author
config/packages/jsonapi.yaml:
jsonapi:
fields:
books: [title, published_at]
include: ['author']
Entity Annotations:
@ORM\Entity or incorrect annotations break serialization.make:entity for consistency.bin/console doctrine:schema:validate to catch issues early.Circular References:
Book <-> Author) can cause infinite loops.exclude in jsonApiResponse or configure yin to ignore cycles:
jsonapi:
yin:
ignore_circular_references: true
Pagination Conflicts:
links.Paginator or KnpPaginatorBundle for compatibility.Caching Headers:
Cache-Control headers can cause performance issues.jsonapi:
response_headers:
Cache-Control: 'public, max-age=3600'
Enable Verbose Logging:
config/packages/dev/jsonapi.yaml:
jsonapi:
debug: true
var/log/dev.log.Inspect Serialized Data:
dd() to debug the object before serialization:
$book = $this->getDoctrine()->getRepository(Book::class)->find(1);
dd($book); // Inspect structure
return $this->jsonApiResponse($book);
Validate JSON:API Output:
curl -H "Accept: application/vnd.api+json" http://localhost/api/books | \
jsonapi-lint --format json
How can I help you explore Laravel packages today?