A lightweight API resource for Laravel that helps you adhere to the JSON:API
standard with support for sparse fieldsets, compound documents, and more baked in.
Note These docs are not designed to introduce you to the
JSON:API
specification and the associated concepts, instead you should head over and read the specification if you are not yet familiar with it. The documentation that follows only covers how to implement the specification via the package.
Table of contents
- PHP:
8.1
,8.2
,8.3
- Laravel:
9.0
,10.0
,11.0
You can install using composer from Packagist.
composer require timacdonald/json-api
The JsonApiResource
class provided by this package is a specialisation of Laravel's Eloquent API resource. All public facing APIs are still accessible; in a controller, for example, you interact with a JsonApiResource
as you would with Laravel's JsonResource
class.
<?php
namespace App\Http\Controllers;
use App\Http\Resources\UserResource;
use App\Models\User;
class UserController
{
public function index()
{
$users = User::with([/* ... */])->paginate();
return UserResource::collection($users);
}
public function show(User $user)
{
$user->load([/* ... */]);
return UserResource::make($user);
}
}
As we make our way through the examples you will see that new APIs are introduced when interacting with the class internally, for example, the toArray()
method is no longer used.
To get started, let's create a UserResource
for our User
model. In our user resource will expose the user's name
, website
, and twitter_handle
in the response.
First we will create a new API resource that extends TiMacDonald\JsonApi\JsonApiResource
.
<?php
namespace App\Http\Resources;
use TiMacDonald\JsonApi\JsonApiResource;
class UserResource extends JsonApiResource
{
//
}
We will now create an $attributes
property and list the model's attributes we want to expose.
<?php
namespace App\Http\Resources;
use TiMacDonald\JsonApi\JsonApiResource;
class UserResource extends JsonApiResource
{
public $attributes = [
'name',
'website',
'twitter_handle',
];
}
When making a request to an endpoint that returns the UserResource
, for example:
Route::get('users/{user}', fn (User $user) => UserResource::make($user));
The following JSON:API
formatted data would be returned:
{
"data": {
"type": "users",
"id": "74812",
"attributes": {
"name": "Tim",
"website": "https://timacdonald.me",
"twitter_handle": "@timacdonald87"
}
}
}
🎉 You have just created your first JSON:API
resource 🎉
Congratulations...and what. a. rush!
We will now dive into adding relationships to your resources, but if you would like to explore more complex attribute features you may like to jump ahead:
Available relationships may be specified in a $relationships
property, similar to the $attributes
property, however you may use a key / value pair to provide the resource class that should be used for the given relationship.
We will make two relationships available on the resource:
$user->team
: a "toOne" /HasOne
relationship.$user->posts
: a "toMany" /HasMany
relationship.
<?php
namespace App\Http\Resources;
use TiMacDonald\JsonApi\JsonApiResource;
class UserResource extends JsonApiResource
{
public $attributes = [
'name',
'website',
'twitter_handle',
];
public $relationships = [
'team' => TeamResource::class,
'posts' => PostResource::class,
];
}
Assuming the key / value pair follows the convention '{myKey}' => {MyKey}Resource::class
, the class may be omitted to streamline things further.
<?php
namespace App\Http\Resources;
use TiMacDonald\JsonApi\JsonApiResource;
class UserResource extends JsonApiResource
{
public $attributes = [
'name',
'website',
'twitter_handle',
];
public $relationships = [
'team',
'posts',
];
}
The client may now request these relationships via the include
query parameter.
GET /users/74812?include=posts,team
Note Relationships are not exposed in the response unless they are requested by the calling client via the
include
query parameter. This is intended and is part of theJSON:API
specification.
{
"data": {
"id": "74812",
"type": "users",
"attributes": {
"name": "Tim",
"website": "https://timacdonald.me",
"twitter_handle": "@timacdonald87"
},
"relationships": {
"posts": {
"data": [
{
"type": "posts",
"id": "25240"
},
{
"type": "posts",
"id": "39974"
}
]
},
"team": {
"data": {
"type": "teams",
"id": "18986"
}
}
}
},
"included": [
{
"id": "25240",
"type": "posts",
"attributes": {
"title": "So what is `JSON:API` all about anyway?",
"content": "...",
"excerpt": "..."
}
},
{
"id": "39974",
"type": "posts",
"attributes": {
"title": "Building an API with Laravel, using the `JSON:API` specification.",
"content": "...",
"excerpt": "..."
}
},
{
"id": "18986",
"type": "teams",
"attributes": {
"name": "Laravel"
}
}
]
}
To learn about more complex relationship features you may like to jump ahead:
This package does not eager load Eloquent relationships. If a relationship is not eagerly loaded, the package will lazy load the relationship on the fly. I highly recommend using Spatie's query builder package which will eager load your models against the JSON:API
query parameter standards.
Spatie provide comprehensive documentation on how to use the package, but I will briefly give an example of how you might use this in a controller.
<?php
namespace App\Http\Controllers;
use App\Http\Resources\UserResource;
use App\Models\User;
use Spatie\QueryBuilder\QueryBuilder;
class UserController
{
public function index()
{
$users = QueryBuilder::for(User::class)
->allowedIncludes(['team', 'posts'])
->paginate();
return UserResource::collection($users);
}
public function show($id)
{
$user = QueryBuilder::for(User::class)
->allowedIncludes(['team', 'posts'])
->findOrFail($id);
return UserResource::make($user);
}
}
We have now covered the basics of exposing attributes and relationships on your resources. We will now cover more advanced topics to give you even greater control.
As we saw in the adding attributes section, the $attributes
property is the fastest way to expose attributes for a resource. In some scenarios you may need greater control over the attributes you are exposing. If that is the case, you may implement the toAttributes()
method. This will grant you access to the current request and allow for conditional logic.
<?php
namespace App\Http\Resources;
use TiMacDonald\JsonApi\JsonApiResource;
class UserResource extends JsonApiResource
{
/**
* @param \Illuminate\Http\Request $request
* @return array<string, mixed>
*/
public function toAttributes($reques