50 KiB
Eloquent: API Resources
- Introduction
- Generating Resources
- Concept Overview
- Resource Collections
- Writing Resources
- Data Wrapping
- Pagination
- Conditional Attributes
- Conditional Relationships
- Adding Meta Data
- Resource Responses
Introduction
When building an API, you may need a transformation layer that sits between your Eloquent models and the JSON responses that are actually returned to your application's users. For example, you may wish to display certain attributes for a subset of users and not others, or you may wish to always include certain relationships in the JSON representation of your models. Eloquent's resource classes allow you to expressively and easily transform your models and model collections into JSON.
Of course, you may always convert Eloquent models or collections to JSON using
their toJson methods; however, Eloquent resources provide more granular and
robust control over the JSON serialization of your models and their
relationships.
Generating Resources
To generate a resource class, you may use the make:resource Artisan command.
By default, resources will be placed in the app/Http/Resources directory of
your application. Resources extend the
Illuminate\Http\Resources\Json\JsonResource class:
1php artisan make:resource UserResource
php artisan make:resource UserResource
Resource Collections
In addition to generating resources that transform individual models, you may generate resources that are responsible for transforming collections of models. This allows your JSON responses to include links and other meta information that is relevant to an entire collection of a given resource.
To create a resource collection, you should use the --collection flag when
creating the resource. Or, including the word Collection in the resource
name will indicate to Laravel that it should create a collection resource.
Collection resources extend the
Illuminate\Http\Resources\Json\ResourceCollection class:
1php artisan make:resource User --collection
2
3php artisan make:resource UserCollection
php artisan make:resource User --collection
php artisan make:resource UserCollection
Concept Overview
This is a high-level overview of resources and resource collections. You are highly encouraged to read the other sections of this documentation to gain a deeper understanding of the customization and power offered to you by resources.
Before diving into all of the options available to you when writing resources,
let's first take a high-level look at how resources are used within Laravel. A
resource class represents a single model that needs to be transformed into a
JSON structure. For example, here is a simple UserResource resource class:
1<?php
2
3namespace App\Http\Resources;
4
5use Illuminate\Http\Request;
6use Illuminate\Http\Resources\Json\JsonResource;
7
8class UserResource extends JsonResource
9{
10 /**
11 * Transform the resource into an array.
12 *
13 * @return array<string, mixed>
14 */
15 public function toArray(Request $request): array
16 {
17 return [
18 'id' => $this->id,
19 'name' => $this->name,
20 'email' => $this->email,
21 'created_at' => $this->created_at,
22 'updated_at' => $this->updated_at,
23 ];
24 }
25}
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
Every resource class defines a toArray method which returns the array of
attributes that should be converted to JSON when the resource is returned as a
response from a route or controller method.
Note that we can access model properties directly from the $this variable.
This is because a resource class will automatically proxy property and method
access down to the underlying model for convenient access. Once the resource
is defined, it may be returned from a route or controller. The resource
accepts the underlying model instance via its constructor:
1use App\Http\Resources\UserResource;
2use App\Models\User;
3
4Route::get('/user/{id}', function (string $id) {
5 return new UserResource(User::findOrFail($id));
6});
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/user/{id}', function (string $id) {
return new UserResource(User::findOrFail($id));
});
For convenience, you may use the model's toResource method, which will use
framework conventions to automatically discover the model's underlying
resource:
1return User::findOrFail($id)->toResource();
return User::findOrFail($id)->toResource();
When invoking the toResource method, Laravel will attempt to locate a
resource that matches the model's name and is optionally suffixed with
Resource within the Http\Resources namespace closest to the model's
namespace.
Resource Collections
If you are returning a collection of resources or a paginated response, you
should use the collection method provided by your resource class when
creating the resource instance in your route or controller:
1use App\Http\Resources\UserResource;
2use App\Models\User;
3
4Route::get('/users', function () {
5 return UserResource::collection(User::all());
6});
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/users', function () {
return UserResource::collection(User::all());
});
Or, for convenience, you may use the Eloquent collection's
toResourceCollection method, which will use framework conventions to
automatically discover the model's underlying resource collection:
1return User::all()->toResourceCollection();
return User::all()->toResourceCollection();
When invoking the toResourceCollection method, Laravel will attempt to
locate a resource collection that matches the model's name and is suffixed
with Collection within the Http\Resources namespace closest to the model's
namespace.
Custom Resource Collections
By default, resource collections do not allow any addition of custom meta data that may need to be returned with your collection. If you would like to customize the resource collection response, you may create a dedicated resource to represent the collection:
1php artisan make:resource UserCollection
php artisan make:resource UserCollection
Once the resource collection class has been generated, you may easily define any meta data that should be included with the response:
1<?php
2
3namespace App\Http\Resources;
4
5use Illuminate\Http\Request;
6use Illuminate\Http\Resources\Json\ResourceCollection;
7
8class UserCollection extends ResourceCollection
9{
10 /**
11 * Transform the resource collection into an array.
12 *
13 * @return array<int|string, mixed>
14 */
15 public function toArray(Request $request): array
16 {
17 return [
18 'data' => $this->collection,
19 'links' => [
20 'self' => 'link-value',
21 ],
22 ];
23 }
24}
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array<int|string, mixed>
*/
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
After defining your resource collection, it may be returned from a route or controller:
1use App\Http\Resources\UserCollection;
2use App\Models\User;
3
4Route::get('/users', function () {
5 return new UserCollection(User::all());
6});
use App\Http\Resources\UserCollection;
use App\Models\User;
Route::get('/users', function () {
return new UserCollection(User::all());
});
Or, for convenience, you may use the Eloquent collection's
toResourceCollection method, which will use framework conventions to
automatically discover the model's underlying resource collection:
1return User::all()->toResourceCollection();
return User::all()->toResourceCollection();
When invoking the toResourceCollection method, Laravel will attempt to
locate a resource collection that matches the model's name and is suffixed
with Collection within the Http\Resources namespace closest to the model's
namespace.
Preserving Collection Keys
When returning a resource collection from a route, Laravel resets the
collection's keys so that they are in numerical order. However, you may add a
preserveKeys property to your resource class indicating whether a
collection's original keys should be preserved:
1<?php
2
3namespace App\Http\Resources;
4
5use Illuminate\Http\Resources\Json\JsonResource;
6
7class UserResource extends JsonResource
8{
9 /**
10 * Indicates if the resource's collection keys should be preserved.
11 *
12 * @var bool
13 */
14 public $preserveKeys = true;
15}
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Indicates if the resource's collection keys should be preserved.
*
* @var bool
*/
public $preserveKeys = true;
}
When the preserveKeys property is set to true, collection keys will be
preserved when the collection is returned from a route or controller:
1use App\Http\Resources\UserResource;
2use App\Models\User;
3
4Route::get('/users', function () {
5 return UserResource::collection(User::all()->keyBy->id);
6});
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/users', function () {
return UserResource::collection(User::all()->keyBy->id);
});
Customizing the Underlying Resource Class
Typically, the $this->collection property of a resource collection is
automatically populated with the result of mapping each item of the collection
to its singular resource class. The singular resource class is assumed to be
the collection's class name without the trailing Collection portion of the
class name. In addition, depending on your personal preference, the singular
resource class may or may not be suffixed with Resource.
For example, UserCollection will attempt to map the given user instances
into the UserResource resource. To customize this behavior, you may override
the $collects property of your resource collection:
1<?php
2
3namespace App\Http\Resources;
4
5use Illuminate\Http\Resources\Json\ResourceCollection;
6
7class UserCollection extends ResourceCollection
8{
9 /**
10 * The resource that this resource collects.
11 *
12 * @var string
13 */
14 public $collects = Member::class;
15}
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* The resource that this resource collects.
*
* @var string
*/
public $collects = Member::class;
}
Writing Resources
If you have not read the concept overview, you are highly encouraged to do so before proceeding with this documentation.
Resources only need to transform a given model into an array. So, each
resource contains a toArray method which translates your model's attributes
into an API friendly array that can be returned from your application's routes
or controllers:
1<?php
2
3namespace App\Http\Resources;
4
5use Illuminate\Http\Request;
6use Illuminate\Http\Resources\Json\JsonResource;
7
8class UserResource extends JsonResource
9{
10 /**
11 * Transform the resource into an array.
12 *
13 * @return array<string, mixed>
14 */
15 public function toArray(Request $request): array
16 {
17 return [
18 'id' => $this->id,
19 'name' => $this->name,
20 'email' => $this->email,
21 'created_at' => $this->created_at,
22 'updated_at' => $this->updated_at,
23 ];
24 }
25}
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
Once a resource has been defined, it may be returned directly from a route or controller:
1use App\Models\User;
2
3Route::get('/user/{id}', function (string $id) {
4 return User::findOrFail($id)->toUserResource();
5});
use App\Models\User;
Route::get('/user/{id}', function (string $id) {
return User::findOrFail($id)->toUserResource();
});
Relationships
If you would like to include related resources in your response, you may add
them to the array returned by your resource's toArray method. In this
example, we will use the PostResource resource's collection method to add
the user's blog posts to the resource response:
1use App\Http\Resources\PostResource;
2use Illuminate\Http\Request;
3
4/**
5 * Transform the resource into an array.
6 *
7 * @return array<string, mixed>
8 */
9public function toArray(Request $request): array
10{
11 return [
12 'id' => $this->id,
13 'name' => $this->name,
14 'email' => $this->email,
15 'posts' => PostResource::collection($this->posts),
16 'created_at' => $this->created_at,
17 'updated_at' => $this->updated_at,
18 ];
19}
use App\Http\Resources\PostResource;
use Illuminate\Http\Request;
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => PostResource::collection($this->posts),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
If you would like to include relationships only when they have already been loaded, check out the documentation on conditional relationships.
Resource Collections
While resources transform a single model into an array, resource collections
transform a collection of models into an array. However, it is not absolutely
necessary to define a resource collection class for each one of your models
since all Eloquent model collections provide a toResourceCollection method
to generate an "ad-hoc" resource collection on the fly:
1use App\Models\User;
2
3Route::get('/users', function () {
4 return User::all()->toResourceCollection();
5});
use App\Models\User;
Route::get('/users', function () {
return User::all()->toResourceCollection();
});
However, if you need to customize the meta data returned with the collection, it is necessary to define your own resource collection:
1<?php
2
3namespace App\Http\Resources;
4
5use Illuminate\Http\Request;
6use Illuminate\Http\Resources\Json\ResourceCollection;
7
8class UserCollection extends ResourceCollection
9{
10 /**
11 * Transform the resource collection into an array.
12 *
13 * @return array<string, mixed>
14 */
15 public function toArray(Request $request): array
16 {
17 return [
18 'data' => $this->collection,
19 'links' => [
20 'self' => 'link-value',
21 ],
22 ];
23 }
24}
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
Like singular resources, resource collections may be returned directly from routes or controllers:
1use App\Http\Resources\UserCollection;
2use App\Models\User;
3
4Route::get('/users', function () {
5 return new UserCollection(User::all());
6});
use App\Http\Resources\UserCollection;
use App\Models\User;
Route::get('/users', function () {
return new UserCollection(User::all());
});
Or, for convenience, you may use the Eloquent collection's
toResourceCollection method, which will use framework conventions to
automatically discover the model's underlying resource collection:
1return User::all()->toResourceCollection();
return User::all()->toResourceCollection();
When invoking the toResourceCollection method, Laravel will attempt to
locate a resource collection that matches the model's name and is suffixed
with Collection within the Http\Resources namespace closest to the model's
namespace.
Data Wrapping
By default, your outermost resource is wrapped in a data key when the
resource response is converted to JSON. So, for example, a typical resource
collection response looks like the following:
1{
2 "data": [
3 {
4 "id": 1,
5 "name": "Eladio Schroeder Sr.",
6 "email": "[[email protected]](/cdn-cgi/l/email-protection)"
7 },
8 {
9 "id": 2,
10 "name": "Liliana Mayert",
11 "email": "[[email protected]](/cdn-cgi/l/email-protection)"
12 }
13 ]
14}
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "[[email protected]](/cdn-cgi/l/email-protection)"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "[[email protected]](/cdn-cgi/l/email-protection)"
}
]
}
If you would like to disable the wrapping of the outermost resource, you
should invoke the withoutWrapping method on the base
Illuminate\Http\Resources\Json\JsonResource class. Typically, you should
call this method from your AppServiceProvider or another service
provider that is loaded on every request to your
application:
1<?php
2
3namespace App\Providers;
4
5use Illuminate\Http\Resources\Json\JsonResource;
6use Illuminate\Support\ServiceProvider;
7
8class AppServiceProvider extends ServiceProvider
9{
10 /**
11 * Register any application services.
12 */
13 public function register(): void
14 {
15 // ...
16 }
17
18 /**
19 * Bootstrap any application services.
20 */
21 public function boot(): void
22 {
23 JsonResource::withoutWrapping();
24 }
25}
<?php
namespace App\Providers;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// ...
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
JsonResource::withoutWrapping();
}
}
The withoutWrapping method only affects the outermost response and will not
remove data keys that you manually add to your own resource collections.
Wrapping Nested Resources
You have total freedom to determine how your resource's relationships are
wrapped. If you would like all resource collections to be wrapped in a data
key, regardless of their nesting, you should define a resource collection
class for each resource and return the collection within a data key.
You may be wondering if this will cause your outermost resource to be wrapped
in two data keys. Don't worry, Laravel will never let your resources be
accidentally double-wrapped, so you don't have to be concerned about the
nesting level of the resource collection you are transforming:
1<?php
2
3namespace App\Http\Resources;
4
5use Illuminate\Http\Resources\Json\ResourceCollection;
6
7class CommentsCollection extends ResourceCollection
8{
9 /**
10 * Transform the resource collection into an array.
11 *
12 * @return array<string, mixed>
13 */
14 public function toArray(Request $request): array
15 {
16 return ['data' => $this->collection];
17 }
18}
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class CommentsCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return ['data' => $this->collection];
}
}
Data Wrapping and Pagination
When returning paginated collections via a resource response, Laravel will
wrap your resource data in a data key even if the withoutWrapping method
has been called. This is because paginated responses always contain meta and
links keys with information about the paginator's state:
1{
2 "data": [
3 {
4 "id": 1,
5 "name": "Eladio Schroeder Sr.",
6 "email": "[[email protected]](/cdn-cgi/l/email-protection)"
7 },
8 {
9 "id": 2,
10 "name": "Liliana Mayert",
11 "email": "[[email protected]](/cdn-cgi/l/email-protection)"
12 }
13 ],
14 "links":{
15 "first": "http://example.com/users?page=1",
16 "last": "http://example.com/users?page=1",
17 "prev": null,
18 "next": null
19 },
20 "meta":{
21 "current_page": 1,
22 "from": 1,
23 "last_page": 1,
24 "path": "http://example.com/users",
25 "per_page": 15,
26 "to": 10,
27 "total": 10
28 }
29}
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "[[email protected]](/cdn-cgi/l/email-protection)"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "[[email protected]](/cdn-cgi/l/email-protection)"
}
],
"links":{
"first": "http://example.com/users?page=1",
"last": "http://example.com/users?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/users",
"per_page": 15,
"to": 10,
"total": 10
}
}
Pagination
You may pass a Laravel paginator instance to the collection method of a
resource or to a custom resource collection:
1use App\Http\Resources\UserCollection;
2use App\Models\User;
3
4Route::get('/users', function () {
5 return new UserCollection(User::paginate());
6});
use App\Http\Resources\UserCollection;
use App\Models\User;
Route::get('/users', function () {
return new UserCollection(User::paginate());
});
Or, for convenience, you may use the paginator's toResourceCollection
method, which will use framework conventions to automatically discover the
paginated model's underlying resource collection:
1return User::paginate()->toResourceCollection();
return User::paginate()->toResourceCollection();
Paginated responses always contain meta and links keys with information
about the paginator's state:
1{
2 "data": [
3 {
4 "id": 1,
5 "name": "Eladio Schroeder Sr.",
6 "email": "[[email protected]](/cdn-cgi/l/email-protection)"
7 },
8 {
9 "id": 2,
10 "name": "Liliana Mayert",
11 "email": "[[email protected]](/cdn-cgi/l/email-protection)"
12 }
13 ],
14 "links":{
15 "first": "http://example.com/users?page=1",
16 "last": "http://example.com/users?page=1",
17 "prev": null,
18 "next": null
19 },
20 "meta":{
21 "current_page": 1,
22 "from": 1,
23 "last_page": 1,
24 "path": "http://example.com/users",
25 "per_page": 15,
26 "to": 10,
27 "total": 10
28 }
29}
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "[[email protected]](/cdn-cgi/l/email-protection)"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "[[email protected]](/cdn-cgi/l/email-protection)"
}
],
"links":{
"first": "http://example.com/users?page=1",
"last": "http://example.com/users?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/users",
"per_page": 15,
"to": 10,
"total": 10
}
}
Customizing the Pagination Information
If you would like to customize the information included in the links or
meta keys of the pagination response, you may define a
paginationInformation method on the resource. This method will receive the
$paginated data and the array of $default information, which is an array
containing the links and meta keys:
1/**
2 * Customize the pagination information for the resource.
3 *
4 * @param \Illuminate\Http\Request $request
5 * @param array $paginated
6 * @param array $default
7 * @return array
8 */
9public function paginationInformation($request, $paginated, $default)
10{
11 $default['links']['custom'] = 'https://example.com';
12
13 return $default;
14}
/**
* Customize the pagination information for the resource.
*
* @param \Illuminate\Http\Request $request
* @param array $paginated
* @param array $default
* @return array
*/
public function paginationInformation($request, $paginated, $default)
{
$default['links']['custom'] = 'https://example.com';
return $default;
}
Conditional Attributes
Sometimes you may wish to only include an attribute in a resource response if
a given condition is met. For example, you may wish to only include a value if
the current user is an "administrator". Laravel provides a variety of helper
methods to assist you in this situation. The when method may be used to
conditionally add an attribute to a resource response:
1/**
2 * Transform the resource into an array.
3 *
4 * @return array<string, mixed>
5 */
6public function toArray(Request $request): array
7{
8 return [
9 'id' => $this->id,
10 'name' => $this->name,
11 'email' => $this->email,
12 'secret' => $this->when($request->user()->isAdmin(), 'secret-value'),
13 'created_at' => $this->created_at,
14 'updated_at' => $this->updated_at,
15 ];
16}
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'secret' => $this->when($request->user()->isAdmin(), 'secret-value'),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
In this example, the secret key will only be returned in the final resource
response if the authenticated user's isAdmin method returns true. If the
method returns false, the secret key will be removed from the resource
response before it is sent to the client. The when method allows you to
expressively define your resources without resorting to conditional statements
when building the array.
The when method also accepts a closure as its second argument, allowing you
to calculate the resulting value only if the given condition is true:
1'secret' => $this->when($request->user()->isAdmin(), function () {
2 return 'secret-value';
3}),
'secret' => $this->when($request->user()->isAdmin(), function () {
return 'secret-value';
}),
The whenHas method may be used to include an attribute if it is actually
present on the underlying model:
1'name' => $this->whenHas('name'),
'name' => $this->whenHas('name'),
Additionally, the whenNotNull method may be used to include an attribute in
the resource response if the attribute is not null:
1'name' => $this->whenNotNull($this->name),
'name' => $this->whenNotNull($this->name),
Merging Conditional Attributes
Sometimes you may have several attributes that should only be included in the
resource response based on the same condition. In this case, you may use the
mergeWhen method to include the attributes in the response only when the
given condition is true:
1/**
2 * Transform the resource into an array.
3 *
4 * @return array<string, mixed>
5 */
6public function toArray(Request $request): array
7{
8 return [
9 'id' => $this->id,
10 'name' => $this->name,
11 'email' => $this->email,
12 $this->mergeWhen($request->user()->isAdmin(), [
13 'first-secret' => 'value',
14 'second-secret' => 'value',
15 ]),
16 'created_at' => $this->created_at,
17 'updated_at' => $this->updated_at,
18 ];
19}
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
$this->mergeWhen($request->user()->isAdmin(), [
'first-secret' => 'value',
'second-secret' => 'value',
]),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
Again, if the given condition is false, these attributes will be removed
from the resource response before it is sent to the client.
The mergeWhen method should not be used within arrays that mix string and
numeric keys. Furthermore, it should not be used within arrays with numeric
keys that are not ordered sequentially.
Conditional Relationships
In addition to conditionally loading attributes, you may conditionally include relationships on your resource responses based on if the relationship has already been loaded on the model. This allows your controller to decide which relationships should be loaded on the model and your resource can easily include them only when they have actually been loaded. Ultimately, this makes it easier to avoid "N+1" query problems within your resources.
The whenLoaded method may be used to conditionally load a relationship. In
order to avoid unnecessarily loading relationships, this method accepts the
name of the relationship instead of the relationship itself:
1use App\Http\Resources\PostResource;
2
3/**
4 * Transform the resource into an array.
5 *
6 * @return array<string, mixed>
7 */
8public function toArray(Request $request): array
9{
10 return [
11 'id' => $this->id,
12 'name' => $this->name,
13 'email' => $this->email,
14 'posts' => PostResource::collection($this->whenLoaded('posts')),
15 'created_at' => $this->created_at,
16 'updated_at' => $this->updated_at,
17 ];
18}
use App\Http\Resources\PostResource;
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => PostResource::collection($this->whenLoaded('posts')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
In this example, if the relationship has not been loaded, the posts key will
be removed from the resource response before it is sent to the client.
Conditional Relationship Counts
In addition to conditionally including relationships, you may conditionally include relationship "counts" on your resource responses based on if the relationship's count has been loaded on the model:
1new UserResource($user->loadCount('posts'));
new UserResource($user->loadCount('posts'));
The whenCounted method may be used to conditionally include a relationship's
count in your resource response. This method avoids unnecessarily including
the attribute if the relationships' count is not present:
1/**
2 * Transform the resource into an array.
3 *
4 * @return array<string, mixed>
5 */
6public function toArray(Request $request): array
7{
8 return [
9 'id' => $this->id,
10 'name' => $this->name,
11 'email' => $this->email,
12 'posts_count' => $this->whenCounted('posts'),
13 'created_at' => $this->created_at,
14 'updated_at' => $this->updated_at,
15 ];
16}
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts_count' => $this->whenCounted('posts'),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
In this example, if the posts relationship's count has not been loaded, the
posts_count key will be removed from the resource response before it is sent
to the client.
Other types of aggregates, such as avg, sum, min, and max may also be
conditionally loaded using the whenAggregated method:
1'words_avg' => $this->whenAggregated('posts', 'words', 'avg'),
2'words_sum' => $this->whenAggregated('posts', 'words', 'sum'),
3'words_min' => $this->whenAggregated('posts', 'words', 'min'),
4'words_max' => $this->whenAggregated('posts', 'words', 'max'),
'words_avg' => $this->whenAggregated('posts', 'words', 'avg'),
'words_sum' => $this->whenAggregated('posts', 'words', 'sum'),
'words_min' => $this->whenAggregated('posts', 'words', 'min'),
'words_max' => $this->whenAggregated('posts', 'words', 'max'),
Conditional Pivot Information
In addition to conditionally including relationship information in your
resource responses, you may conditionally include data from the intermediate
tables of many-to-many relationships using the whenPivotLoaded method. The
whenPivotLoaded method accepts the name of the pivot table as its first
argument. The second argument should be a closure that returns the value to be
returned if the pivot information is available on the model:
1/**
2 * Transform the resource into an array.
3 *
4 * @return array<string, mixed>
5 */
6public function toArray(Request $request): array
7{
8 return [
9 'id' => $this->id,
10 'name' => $this->name,
11 'expires_at' => $this->whenPivotLoaded('role_user', function () {
12 return $this->pivot->expires_at;
13 }),
14 ];
15}
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'expires_at' => $this->whenPivotLoaded('role_user', function () {
return $this->pivot->expires_at;
}),
];
}
If your relationship is using a [custom intermediate table
model](/docs/12.x/eloquent-relationships#defining-custom-intermediate-table-
models), you may pass an instance of the intermediate table model as the first
argument to the whenPivotLoaded method:
1'expires_at' => $this->whenPivotLoaded(new Membership, function () {
2 return $this->pivot->expires_at;
3}),
'expires_at' => $this->whenPivotLoaded(new Membership, function () {
return $this->pivot->expires_at;
}),
If your intermediate table is using an accessor other than pivot, you may
use the whenPivotLoadedAs method:
1/**
2 * Transform the resource into an array.
3 *
4 * @return array<string, mixed>
5 */
6public function toArray(Request $request): array
7{
8 return [
9 'id' => $this->id,
10 'name' => $this->name,
11 'expires_at' => $this->whenPivotLoadedAs('subscription', 'role_user', function () {
12 return $this->subscription->expires_at;
13 }),
14 ];
15}
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'expires_at' => $this->whenPivotLoadedAs('subscription', 'role_user', function () {
return $this->subscription->expires_at;
}),
];
}
Adding Meta Data
Some JSON API standards require the addition of meta data to your resource and
resource collections responses. This often includes things like links to the
resource or related resources, or meta data about the resource itself. If you
need to return additional meta data about a resource, include it in your
toArray method. For example, you might include links information when
transforming a resource collection:
1/**
2 * Transform the resource into an array.
3 *
4 * @return array<string, mixed>
5 */
6public function toArray(Request $request): array
7{
8 return [
9 'data' => $this->collection,
10 'links' => [
11 'self' => 'link-value',
12 ],
13 ];
14}
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
When returning additional meta data from your resources, you never have to
worry about accidentally overriding the links or meta keys that are
automatically added by Laravel when returning paginated responses. Any
additional links you define will be merged with the links provided by the
paginator.
Top Level Meta Data
Sometimes you may wish to only include certain meta data with a resource
response if the resource is the outermost resource being returned. Typically,
this includes meta information about the response as a whole. To define this
meta data, add a with method to your resource class. This method should
return an array of meta data to be included with the resource response only
when the resource is the outermost resource being transformed:
1<?php
2
3namespace App\Http\Resources;
4
5use Illuminate\Http\Resources\Json\ResourceCollection;
6
7class UserCollection extends ResourceCollection
8{
9 /**
10 * Transform the resource collection into an array.
11 *
12 * @return array<string, mixed>
13 */
14 public function toArray(Request $request): array
15 {
16 return parent::toArray($request);
17 }
18
19 /**
20 * Get additional data that should be returned with the resource array.
21 *
22 * @return array<string, mixed>
23 */
24 public function with(Request $request): array
25 {
26 return [
27 'meta' => [
28 'key' => 'value',
29 ],
30 ];
31 }
32}
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return parent::toArray($request);
}
/**
* Get additional data that should be returned with the resource array.
*
* @return array<string, mixed>
*/
public function with(Request $request): array
{
return [
'meta' => [
'key' => 'value',
],
];
}
}
Adding Meta Data When Constructing Resources
You may also add top-level data when constructing resource instances in your
route or controller. The additional method, which is available on all
resources, accepts an array of data that should be added to the resource
response:
1return User::all()
2 ->load('roles')
3 ->toResourceCollection()
4 ->additional(['meta' => [
5 'key' => 'value',
6 ]]);
return User::all()
->load('roles')
->toResourceCollection()
->additional(['meta' => [
'key' => 'value',
]]);
Resource Responses
As you have already read, resources may be returned directly from routes and controllers:
1use App\Models\User;
2
3Route::get('/user/{id}', function (string $id) {
4 return User::findOrFail($id)->toResource();
5});
use App\Models\User;
Route::get('/user/{id}', function (string $id) {
return User::findOrFail($id)->toResource();
});
However, sometimes you may need to customize the outgoing HTTP response before
it is sent to the client. There are two ways to accomplish this. First, you
may chain the response method onto the resource. This method will return an
Illuminate\Http\JsonResponse instance, giving you full control over the
response's headers:
1use App\Http\Resources\UserResource;
2use App\Models\User;
3
4Route::get('/user', function () {
5 return User::find(1)
6 ->toResource()
7 ->response()
8 ->header('X-Value', 'True');
9});
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/user', function () {
return User::find(1)
->toResource()
->response()
->header('X-Value', 'True');
});
Alternatively, you may define a withResponse method within the resource
itself. This method will be called when the resource is returned as the
outermost resource in a response:
1<?php
2
3namespace App\Http\Resources;
4
5use Illuminate\Http\JsonResponse;
6use Illuminate\Http\Request;
7use Illuminate\Http\Resources\Json\JsonResource;
8
9class UserResource extends JsonResource
10{
11 /**
12 * Transform the resource into an array.
13 *
14 * @return array<string, mixed>
15 */
16 public function toArray(Request $request): array
17 {
18 return [
19 'id' => $this->id,
20 ];
21 }
22
23 /**
24 * Customize the outgoing response for the resource.
25 */
26 public function withResponse(Request $request, JsonResponse $response): void
27 {
28 $response->header('X-Value', 'True');
29 }
30}
<?php
namespace App\Http\Resources;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
];
}
/**
* Customize the outgoing response for the resource.
*/
public function withResponse(Request $request, JsonResponse $response): void
{
$response->header('X-Value', 'True');
}
}