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:
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:
php artisan make:resource User --collection
php artisan make:resource UserCollection
Concept Overview
Note:
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:
<?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:
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/user/{id}', function (string $id) {
return new UserResource(User::findOrFail($id));
});
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:
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/users', function () {
return UserResource::collection(User::all());
});
Note that this does 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:
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:
<?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:
use App\Http\Resources\UserCollection;
use App\Models\User;
Route::get('/users', function () {
return new UserCollection(User::all());
});
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:
<?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:
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:
<?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
Note:
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:
<?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:
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/user/{id}', function (string $id) {
return new UserResource(User::findOrFail($id));
});
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:
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,
];
}
Note:
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 resources provide a
collection
method to generate an
"ad-hoc" resource collection on the fly:
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/users', function () {
return UserResource::collection(User::all());
});
However, if you need to customize the meta data returned with the collection, it is necessary to define your own resource collection:
<?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:
use App\Http\Resources\UserCollection;
use App\Models\User;
Route::get('/users', function () {
return new UserCollection(User::all());
});
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:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28@example.com"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort@example.com"
}
]
}
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:
<?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();
}
}
Warning!
ThewithoutWrapping
method only affects the outermost response and will not removedata
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:
<?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:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28@example.com"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort@example.com"
}
],
"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:
use App\Http\Resources\UserCollection;
use App\Models\User;
Route::get('/users', function () {
return new UserCollection(User::paginate());
});
Paginated responses always contain meta
and
links
keys with information about the
paginator's state:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28@example.com"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort@example.com"
}
],
"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:
/**
* 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:
/**
* 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
:
'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:
'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:
'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
:
/**
* 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.
Warning!
ThemergeWhen
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:
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:
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:
/**
* 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:
'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:
/**
* 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, you may pass an
instance of the intermediate table model as the first
argument to the whenPivotLoaded
method:
'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:
/**
* 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 link
information when transforming
a resource collection:
/**
* 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:
<?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:
return (new UserCollection(User::all()->load('roles')))
->additional(['meta' => [
'key' => 'value',
]]);
Resource Responses
As you have already read, resources may be returned directly from routes and controllers:
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/user/{id}', function (string $id) {
return new UserResource(User::findOrFail($id));
});
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:
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/user', function () {
return (new UserResource(User::find(1)))
->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:
<?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');
}
}