Installation
composer require timacdonald/json-api
Publish the config (if needed):
php artisan vendor:publish --provider="TimMacDonald\JsonApi\JsonApiServiceProvider"
Create a Resource
Generate a resource class for your model (e.g., User):
php artisan make:json-api User
This creates app/Http/Resources/UserResource.php.
Basic Usage In your controller, return the resource:
use App\Http\Resources\UserResource;
public function show(User $user)
{
return new UserResource($user);
}
// routes/api.php
Route::get('/users/{user}', [UserController::class, 'show']);
// app/Http/Controllers/UserController.php
public function show(User $user)
{
return new UserResource($user);
}
This automatically adheres to JSON:API standards, returning:
{
"data": {
"type": "users",
"id": "1",
"attributes": {
"name": "John Doe",
"email": "john@example.com"
}
}
}
Index (List)
public function index()
{
return UserResource::collection(User::all());
}
Returns paginated data with links and meta by default.
Store (Create)
public function store(Request $request)
{
$user = User::create($request->validated());
return new UserResource($user, 201);
}
Update/Delete
public function update(Request $request, User $user)
{
$user->update($request->validated());
return new UserResource($user);
}
public function destroy(User $user)
{
$user->delete();
return response()->noContent();
}
Request specific fields via fields query parameter:
// GET /users?fields[users]=name,email
return new UserResource($user);
Resource must define fields:
public static $fields = ['name', 'email', 'created_at'];
Define relationships in the resource:
public function toRelationships()
{
return [
'posts' => PostResource::collection($this->posts),
'author' => new UserResource($this->author)
];
}
Eager-load relationships in the controller:
public function show(User $user)
{
return new UserResource($user->load('posts', 'author'));
}
Include related resources in the response:
// GET /users/1?include=posts,author
return new UserResource($user->load('posts', 'author'));
Override default behavior:
public function toArray($request)
{
return [
'id' => $this->id,
'custom_key' => 'custom_value',
];
}
Eager Loading
load() in the controller or with() in the resource:
public static $with = ['posts']; // Auto-loads in every request
Fieldset Mismatches
$fields is not defined, all attributes are returned.$fields are silently ignored (no error).Relationship Resource Guessing
Post → PostResource).resourceClass():
public function posts()
{
return $this->belongsToMany(Post::class)->resourceClass(PostResource::class);
}
Meta/Data Conflicts
meta or data to prevent conflicts with JSON:API top-level keys.Check the Raw Output
Use dd($resource->resolve()) to inspect the resolved resource before serialization.
Enable JSON:API Debugging
Set JSON_API_DEBUG in .env to log fieldset/relationship issues:
JSON_API_DEBUG=true
Reuse Resources Extend base resources for shared logic:
class BaseResource extends JsonApiResource
{
public function toMeta()
{
return ['version' => '1.0'];
}
}
Custom Error Handling
Override failedValidation() for API-specific errors:
public function failedValidation(Validator $validator)
{
return response()->json([
'errors' => $validator->errors(),
], 422);
}
Performance
select() to limit loaded columns:
return UserResource::collection(User::select(['id', 'name'])->get());
return Cache::remember("user-{$user->id}", now()->addHours(1), function() use ($user) {
return new UserResource($user);
});
Testing
Use JsonApiTestCase for assertions:
$response = $this->getJson('/users/1?fields[users]=name');
$response->assertJsonStructure([
'data' => [
'type', 'id', 'attributes' => ['name']
]
]);
Extending the Package
JsonApiServiceProvider:
JsonApi::directive('custom', function ($resource) {
$resource->addAttribute('custom_field', 'value');
});
JsonApi::extend() to modify the resource class dynamically.How can I help you explore Laravel packages today?