Skip to content
Laradoc Website

10 Tips For Using Laravel API Resources

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!