Laravel API resources are a great way to standardize your API responses by adding a transformation layer between your models and the responses. They provide fine-grained and robust control over how your models and their relationships should be serialized as JSON.
1. Data Wrapping Can Be Disabled
By default, a resource is automatically wrapped in a data key once the JSON serialization is done.
{
"data": [
{
"id": 1,
"name": "John Doe",
"email": "john@doe.com"
},
{
"id": 2,
"name": "Jane Doe",
"email": "jane@doe.com"
}
]
}
This wrapping is useful for including additional keys, like links
and meta
, when using pagination. However, it may only be necessary for some projects.
To disable it, add JsonResource::withoutWrapping()
into a service provider of your Laravel 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()
{
//
}
/**
* Bootstrap any application services.
*/
public function boot()
{
JsonResource::withoutWrapping();
}
}
2. Passing a Paginator
Laravel's paginator instances work great with API resources!
When passing a LengthAwarePaginator
instance to your resource, meta
and links
keys will be included in the response and contain pagination information.
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/users', function () {
return new UserResource::collection(User::paginate());
});
And you get the following response:
{
"data": [
{
"id": 21,
"name": "John Doe",
"email": "john@doe.com"
},
{
"id": 22,
"name": "Jane Doe",
"email": "jane@doe.com"
},
// ...
],
"links": {
"first": "http://example.com/pagination?page=1",
"last": "http://example.com/pagination?page=8",
"prev": "http://example.com/pagination?page=2",
"next": "http://example.com/pagination?page=4"
},
"meta": {
"current_page": 3,
"from": 21,
"last_page": 8,
"path": "http://example.com/pagination",
"per_page": 10,
"to": 30,
"total": 86
}
}
3. Conditionnal Attributes
Laravel provides helpful methods to add conditional attributes in your response. The simpler one is $this->when()
to include an attribute in the response only if a given condition returns true.
<?php
public function toArray($request)
{
return [
'id' => $this->id,
'question' => $this->question,
'choices' => $this->when($this->type === 'multiple-choice', $this->options['choices']),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
In a similar way, you can also use $this->whenNull()
, $this->whenNotNull()
, $this->whenAppended()
and $this->mergeWhen()
and $this->mergeUnless()
.
Let's see how we can implement it to return patient-sensitive data only when we explicitly ask for it.
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class PatientResource extends JsonResource
{
protected $showSensitiveAttributes = false;
/**
* Allow sensitive information to be returned.
*/
public function withSensitiveAttributes(bool $value = true): self
{
$this->showSensitiveAttributes = $value;
return $this;
}
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'email' => $this->email,
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'sex' => $this->sex,
'phone' => $this->phone,
$this->mergeWhen($this->showSensitiveAttributes, [
'address' => $this->address,
'birthdate' => $this->birthdate->format('Y-m-d'),
'social_security_number' => $this->social_security_number,
]),
'created_at' => $this->created_at,
];
}
}
We can now call withSensitiveAttributes()
to include patient-sensitive data in the response. The patient's address, birthdate, and SSN will not be returned if we don't.
<?php
Route::get('/patients/{patient}', function (User $user) {
$isAdmin = Auth::user()->isAdmin();
return PatientResource::make($user)->withSensitiveAttributes($isAdmin);
});
4. Conditional Relationships
In an API resource, it is possible to return attributes only if Eloquent relationships are loaded. This is important to avoid loading them from your API resources and making SQL requests that create "N+1" issues.
No long speeches; here are some examples of the available methods.
whenLoaded
<?php
public function toArray($request)
{
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,
];
}
whenCounted
<?php
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'comments_count' => $this->whenCounted('comments'),
'created_at' => $this->created_at,
];
}
whenPivotLoaded
<?php
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'expires_at' => $this->whenPivotLoaded('role_user', function () {
return $this->pivot->expires_at;
}),
];
}
5. Customize Response Headers
Laravel API resources are not only for JSON serialization. It can also be an excellent place to customize the headers of the outgoing HTTP responses.
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class AlertResource extends JsonResource
{
/**
* Transform the resource into an array.
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'type' => $this->type,
'level' => $this->level,
'reason' => $this->reason,
'resolved_at' => $this->resolved_at,
'created_at' => $this->created_at,
];
}
/**
* Customize the outgoing response for the resource.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
*/
public function withResponse($request, $response): void
{
$response->header('X-Value', 'True');
}
}
6. Meta Data
Using Laravel API resources, adding top-level metadata is a breeze.
Add with()
method to the API resource. For example:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'email' => $this->email,
'created_at' => $this->created_at,
];
}
/**
* Get additional data that should be returned with the resource array.
*/
public function with($request): array
{
return [
'meta' => [
'relationships' => [
'posts',
'files',
'comments',
],
],
];
}
}
With these meta data, we indicate to the API consumer what relationships he could load for this entity.
{
"data": {
"id": 1705,
"email": "brain.goodwin@example.net",
"created_at": "2022-12-23T15:51:23.000000Z"
},
"meta": {
"relationships": [
"posts",
"files",
"comments"
]
}
}
You may also call the additional()
method on your API resource instance if you need to define meta data at the controller level.
<?php
public function show(User $user)
{
return UserResource::make($user)->additional([
'meta' => [
'key' => 'value',
],
]);
});
7. Set JSON Encoding Flags
You may be used to pass encoding flags when using the json_encode()
function, like JSON_PRETTY_PRINT
or JSON_NUMERIC_CHECK
, for example. It's possible to specify these same flags within an API resource; they will be used when the object is serialized to JSON.
To do this, add the jsonOptions
method:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'email' => $this->email,
'created_at' => $this->created_at,
];
}
/**
* Get the JSON serialization options that should be applied.
*/
public function jsonOptions(): int
{
return JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK;
}
}
Accepted options are listed on the PHP documentation.
8. Add Autocompletion
You may have noticed that your methods or property calls made within an API resource are forwarded to your model. These two implementations are equivalent:
<?php
public function toArray($request): array
{
return [
'id' => $this->id,
'email' => $this->email,
'created_at' => $this->created_at,
];
}
And:
<?php
public function toArray($request): array
{
return [
'id' => $this->resource->id,
'email' => $this->resource->email,
'created_at' => $this->resource->created_at,
];
}
We usually use the first option, made possible because JsonResource
implements the Illuminate\Http\Resources\DelegatesToResource
trait. The problem is that IDEs don't understand this, so the auto-completion is lost.
You can solve this by adding @mixin YourModel
to the resource's DocBlock.
<?php
namespace App\Http\Resources;
use App\Models\User;
use Illuminate\Http\Resources\Json\JsonResource;
/** @mixin User */
class UserResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'email' => $this->email,
'created_at' => $this->created_at,
];
}
}
9. Dedicated Resource For Datetimes
It can be helpful to offer various formats for representing dates and times. For example, you may need to provide two formats: a human-readable time difference ("2 hours ago") and a JSON-formatted datetime ("2022-12-23T23:15:30.000Z").
A clever way of doing it is to create a separate JsonResource
for datetimes. Resources don't accept only Eloquent models; you can use them with other objects! We can create a resource for Carbon
instances.
<?php
class DateTimeResource extends JsonRsource
{
public function toArray($request)
{
return [
'datetime' => $this->toISOString(),
'human_diff' => $this->diffForHumans(),
'human' => $this->toDayDateTimeString(),
];
}
}
Use it with your Carbon
instances:
<?php
class UserResource extends JsonRsource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => new DateTimeResource($this->created_at),
];
}
}
And we get the following response:
{
"data": {
"id": 3,
"email": "vrunolfsson@example.net",
"created_at": {
"datetime": "2022-12-26T17:50:22.000000Z",
"human_diff": "37 minutes ago",
"human": "Mon, Dec 26, 2022 5:50 PM"
}
}
}
10. It Pairs Well With Inertia
You know what? JsonResource
can be used even if you're not coding an API!
When building apps with Inertia – a library to build SPAs using classic server-side routing and controllers – you always pass data from your Laravel controllers to React or Vue components. Even without API, you need a transformation layer between your models and front-end components to avoid leaking sensitive information.
The good news is that when Inertia renders a component, the Inertia::render()
method supports API resources, so that they can be used here!
Take a look at a Laradoc controller where I'm doing that.
<?php
public function showCollaborators(Project $project)
{
$this->authorize('view', $project);
$invitations = $project->invitations()->pending()->get();
return Inertia::render('Projects/Collaborators', [
'project' => ProjectResource::make($project),
'owner' => UserResource::make($project->owner),
'collaborators' => UserResource::collection($project->collaborators),
'invitations' => InvitationResource::collection($invitations),
]);
}
API resources are a fantastic feature of Laravel, and I hope you've learned some new insights on how to use them best.
We love helping people build their APIs with Laravel, and we help them generate API documentation so they can focus on their code. Feel free to take a look!