本文へ移動
Laravel Tips

API リソース

モデルを API 向けの JSON に作り変える「API リソース」の作り方と、条件つきの項目・ページ送り・メタ情報・JSON:API 形式・レスポンスの調整を説明します。

API(ほかのプログラムにデータを渡す窓口)を作るとき、データベースのモデルをそのまま外へ出したくないことがあります。たとえば、ある人にだけ見せたい項目があったり、関連するデータをいつも付けたかったりする場合です。API リソースは、モデルと、実際に返す JSON(データを文字で表す形式)のあいだに入って、「どの項目をどんな形で返すか」を決めるクラスです。ふつうに toJson で変えることもできますが、リソースを使うと、より細かく安全に決められます。

リソースを作る#

make:resource という Artisan コマンド(php artisan で動かす Laravel のコマンド)で、リソースのクラスを作れます。app/Http/Resources に置かれ、Illuminate\Http\Resources\Json\JsonResource を継承します(このクラスをもとにして作られます)。

bash
php artisan make:resource UserResource

リソースコレクション#

モデル1つではなく、モデルの集まりを作り変えるリソースコレクションも作れます。集まり全体に関わるリンクやメタ情報(データについての補足)を JSON に入れたいときに便利です。

作るときに --collection を付けるか、名前に Collection を入れます。Illuminate\Http\Resources\Json\ResourceCollection を継承します。

bash
php artisan make:resource User --collection

php artisan make:resource UserCollection

全体のイメージ#

補足

ここは、リソースとリソースコレクションの大まかな説明です。あとの節も読むと、細かい調整のしかたが分かります。

リソースのクラスは、「1つのモデルを、どんな JSON にするか」を表します。たとえば、簡単な UserResource は次のとおりです。

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    /**
     * リソースを配列に変える
     *
     * @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,
        ];
    }
}

どのリソースにも toArray メソッドがあります。ルートやコントローラーからリソースを返すと、ここで返した配列が JSON に変わります。

$this から、モデルのプロパティを直接使えます。リソースが、プロパティやメソッドへのアクセスを、中のモデルに引き渡してくれるからです。定義したら、ルートやコントローラーから返せます。リソースを作るときに、モデルを渡します。

php
use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/user/{id}', function (string $id) {
    return new UserResource(User::findOrFail($id));
});

モデルの toResource メソッドを使うと、Laravel の決まりに従って、リソースを自動で見つけてくれます。

php
return User::findOrFail($id)->toResource();

toResource は、モデルと同じ名前(うしろに Resource が付いていてもよい)のリソースを、モデルの名前空間(App\Models のような、クラスの住所)にいちばん近い Http\Resources から探します。

名前や置き場所が決まりと合わないときは、モデルに UseResource 属性(クラスの前に書く印)を付けて、既定のリソースを指定できます。

php
<?php

namespace App\Models;

use App\Http\Resources\CustomUserResource;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Attributes\UseResource;

#[UseResource(CustomUserResource::class)]
class User extends Model
{
    // ...
}

toResource にクラスを渡して指定することもできます。

php
return User::findOrFail($id)->toResource(CustomUserResource::class);

リソースコレクションの使い方#

リソースの集まりや、ページ送りの結果を返すときは、リソースクラスの collection メソッドを使います。

php
use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/users', function () {
    return UserResource::collection(User::all());
});

Eloquent のコレクションの toResourceCollection メソッドを使うと、リソースコレクションを自動で見つけてくれます。

php
return User::all()->toResourceCollection();

toResourceCollection は、モデルと同じ名前のうしろに Collection が付いたリソースコレクションを、モデルの名前空間にいちばん近い Http\Resources から探します。

名前や置き場所が決まりと合わないときは、UseResourceCollection 属性で指定します。

php
<?php

namespace App\Models;

use App\Http\Resources\CustomUserCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Attributes\UseResourceCollection;

#[UseResourceCollection(CustomUserCollection::class)]
class User extends Model
{
    // ...
}

toResourceCollection にクラスを渡しても指定できます。

php
return User::all()->toResourceCollection(CustomUserCollection::class);

専用のリソースコレクション#

ふつうのリソースコレクションには、返すデータに決まったメタ情報を足せません。足したいときは、専用のリソースコレクションを作ります。

bash
php artisan make:resource UserCollection

作ったクラスに、レスポンスに入れたいメタ情報を書けます。

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    /**
     * リソースコレクションを配列に変える
     *
     * @return array<int|string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'data' => $this->collection,
            'links' => [
                'self' => 'link-value',
            ],
        ];
    }
}

ルートやコントローラーから返せます。

php
use App\Http\Resources\UserCollection;
use App\Models\User;

Route::get('/users', function () {
    return new UserCollection(User::all());
});

ここでも、toResourceCollection で自動で見つけさせられます。

php
return User::all()->toResourceCollection();

この場合も、モデルと同じ名前のうしろに Collection が付いたクラスを、Http\Resources から探します。

コレクションのキーを残す#

ルートからリソースコレクションを返すと、Laravel はコレクションのキーを 0, 1, 2… の順に振り直します。元のキーを残したいときは、リソースのクラスに PreserveKeys 属性を付けます。

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Attributes\PreserveKeys;
use Illuminate\Http\Resources\Json\JsonResource;

#[PreserveKeys]
class UserResource extends JsonResource
{
    // ...
}

preserveKeys が true になっていると、ルートやコントローラーから返したときに、キーが残ります。

php
use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/users', function () {
    return UserResource::collection(User::all()->keyBy->id);
});

中身の変換に使うリソースを変える#

ふつう、リソースコレクションの $this->collection には、各要素を「1つ用のリソース」に変えたものが入ります。1つ用のリソースの名前は、コレクションのクラス名から、うしろの Collection を取ったものです(うしろに Resource が付いていても付いていなくてもかまいません)。

たとえば、UserCollection は、UserResource で変換しようとします。変えたいときは、リソースコレクションに Collects 属性を付けます。

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Attributes\Collects;
use Illuminate\Http\Resources\Json\ResourceCollection;

#[Collects(Member::class)]
class UserCollection extends ResourceCollection
{
    // ...
}

リソースを書く#

補足

まだ前の節「全体のイメージ」を読んでいないなら、先に読んでおくと分かりやすくなります。

リソースの仕事は、モデルを配列に変えることだけです。どのリソースにも toArray メソッドがあり、モデルの属性を、API に向いた配列にします。

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    /**
     * リソースを配列に変える
     *
     * @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,
        ];
    }
}

定義したら、ルートやコントローラーから、そのまま返せます。

php
use App\Models\User;

Route::get('/user/{id}', function (string $id) {
    return User::findOrFail($id)->toResource();
});

リレーションを入れる#

関連するリソース(リレーション)も、レスポンスに入れられます。toArray の配列に足します。次の例は、PostResource の collection を使って、ユーザーの記事を入れます。

php
use App\Http\Resources\PostResource;
use Illuminate\Http\Request;

/**
 * リソースを配列に変える
 *
 * @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,
    ];
}

補足

すでに読み込んであるリレーションだけを入れたいときは、あとの「条件つきのリレーション」を見てください。

リソースコレクションを書く#

リソースは1つのモデルを配列に変え、リソースコレクションはモデルの集まりを配列に変えます。ただし、モデルごとにリソースコレクションのクラスを作る必要はありません。Eloquent のコレクションには toResourceCollection があり、その場で「ad-hoc」(臨時)のリソースコレクションを作れます。

php
use App\Models\User;

Route::get('/users', function () {
    return User::all()->toResourceCollection();
});

ただし、集まりといっしょに返すメタ情報を変えたいなら、専用のリソースコレクションを定義する必要があります。

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    /**
     * リソースコレクションを配列に変える
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'data' => $this->collection,
            'links' => [
                'self' => 'link-value',
            ],
        ];
    }
}

1つ用のリソースと同じく、ルートやコントローラーから、そのまま返せます。

php
use App\Http\Resources\UserCollection;
use App\Models\User;

Route::get('/users', function () {
    return new UserCollection(User::all());
});

ここでも toResourceCollection で自動で見つけさせられます。

php
return User::all()->toResourceCollection();

データを data で包む(Data Wrapping)#

いちばん外側のリソースは、JSON にするとき、ふつう data というキーで包まれます。たとえば、リソースコレクションのレスポンスは、次のようになります。

json
{
    "data": [
        {
            "id": 1,
            "name": "Eladio Schroeder Sr.",
            "email": "therese28@example.com"
        },
        {
            "id": 2,
            "name": "Liliana Mayert",
            "email": "evandervort@example.com"
        }
    ]
}

いちばん外側を包みたくないときは、Illuminate\Http\Resources\Json\JsonResource の withoutWrapping を呼びます。リクエストのたびに読み込まれる AppServiceProvider などのサービスプロバイダの中で呼ぶのがふつうです。

php
<?php

namespace App\Providers;

use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * アプリのサービスを登録する
     */
    public function register(): void
    {
        // ...
    }

    /**
     * アプリのサービスを起動する
     */
    public function boot(): void
    {
        JsonResource::withoutWrapping();
    }
}

注意

withoutWrapping が効くのは、いちばん外側のレスポンスだけです。自分で書いたリソースコレクションの中に入れた data キーは、消えません。

入れ子のリソースを包む#

入れ子のリソースを、どう包むかは自由に決められます。入れ子の深さにかかわらず、すべてのリソースコレクションを data で包みたいなら、リソースごとにリソースコレクションのクラスを作り、data キーに入れて返します。

「いちばん外側が二重に data で包まれるのでは」と心配になりますが、Laravel は二重に包まないので、入れ子の深さを気にしなくてかまいません。

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class CommentsCollection extends ResourceCollection
{
    /**
     * リソースコレクションを配列に変える
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return ['data' => $this->collection];
    }
}

データの包みとページ送り#

ページ送りの結果をリソースで返すときは、withoutWrapping を呼んでいても、data で包まれます。ページ送りのレスポンスには、ページの状態を表す meta と links が必ず入るからです。

json
{
    "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)#

Laravel のページ送りの結果(ペジネータ)を、リソースの collection や、専用のリソースコレクションに渡せます。

php
use App\Http\Resources\UserCollection;
use App\Models\User;

Route::get('/users', function () {
    return new UserCollection(User::paginate());
});

ペジネータの toResourceCollection でも、リソースコレクションを自動で見つけられます。

php
return User::paginate()->toResourceCollection();

ページ送りのレスポンスには、ページの状態を表す meta と links が必ず入ります。

json
{
    "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
    }
}

ページ送りの情報を変える#

links や meta に入る情報を変えたいときは、リソースに paginationInformation メソッドを定義します。ページ送りのデータ $paginated と、links と meta が入った既定の情報 $default を受け取ります。

php
/**
 * ページ送りの情報を変える
 *
 * @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)#

ある条件に合うときだけ、レスポンスに項目を入れたいことがあります。たとえば、「管理者」のときだけ値を入れたい場合です。そのためのメソッドがいくつかあります。

メソッド 説明
when 条件が true のときだけ、項目を入れる
whenHas 元のモデルに、その属性があるときだけ入れる
whenNotNull 値が null ではないときだけ入れる
mergeWhen 条件が true のときだけ、複数の項目をまとめて入れる

when の例です。

php
/**
 * リソースを配列に変える
 *
 * @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,
    ];
}

この例では、ログイン中のユーザーの isAdmin が true のときだけ、secret が返ります。false なら、送る前に secret が取り除かれます。when を使うと、配列を作るときに if を書かなくて済みます。

when の第2引数には、クロージャも渡せます。条件が true のときだけ、値を計算します。

php
'secret' => $this->when($request->user()->isAdmin(), function () {
    return 'secret-value';
}),

whenHas は、元のモデルに、その属性が実際にあるときだけ入れます。

php
'name' => $this->whenHas('name'),

whenNotNull は、値が null ではないときだけ入れます。

php
'name' => $this->whenNotNull($this->name),

条件つきの項目をまとめて入れる#

同じ条件で入れる項目がいくつもあるときは、mergeWhen を使います。

php
/**
 * リソースを配列に変える
 *
 * @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,
    ];
}

条件が false なら、これらの項目は送る前に取り除かれます。

注意

mergeWhen は、文字のキーと数字のキーが混ざった配列の中では使わないでください。数字のキーが順番に並んでいない配列でも使えません。

条件つきのリレーション(Conditional Relationships)#

項目だけでなく、リレーションも、すでにモデルに読み込まれているときだけ入れられます。何を読み込むかをコントローラーに任せ、リソースは、読み込まれたものだけを入れます。これで、リソースの中でも「N+1」問題(問い合わせがむだに増える問題)を避けやすくなります。

whenLoaded を使います。むだに読み込まないよう、リレーションそのものではなく、リレーションの名前を渡します。

php
use App\Http\Resources\PostResource;

/**
 * リソースを配列に変える
 *
 * @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,
    ];
}

この例では、リレーションが読み込まれていなければ、posts は送る前に取り除かれます。

条件つきの件数#

リレーションの「件数」も、読み込まれているときだけ入れられます。

php
new UserResource($user->loadCount('posts'));

whenCounted は、リレーションの件数が読み込まれているときだけ、項目を入れます。

php
/**
 * リソースを配列に変える
 *
 * @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,
    ];
}

この例では、posts の件数が読み込まれていなければ、posts_count は送る前に取り除かれます。

平均(avg)・合計(sum)・最小(min)・最大(max)のような集計も、whenAggregated で、読み込まれているときだけ入れられます。

php
'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'),

条件つきの中間の表の情報#

多対多の中間の表(pivot)の情報も、whenPivotLoaded で、あるときだけ入れられます。第1引数は中間の表の名前、第2引数は、情報があるときに返す値を作るクロージャです。

php
/**
 * リソースを配列に変える
 *
 * @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;
        }),
    ];
}

専用の中間モデルを使っているなら、第1引数に、その中間モデルのインスタンス(クラスから作った実物)を渡せます(リレーションを見てください)。

php
'expires_at' => $this->whenPivotLoaded(new Membership, function () {
    return $this->pivot->expires_at;
}),

中間の表の名前を pivot 以外に変えているなら、whenPivotLoadedAs を使います。

php
/**
 * リソースを配列に変える
 *
 * @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;
        }),
    ];
}

メタ情報を足す#

JSON API の決まりごとによっては、リソースやリソースコレクションのレスポンスに、メタ情報を入れる必要があります。たとえば、リソースや関連するリソースへの links や、リソースそのものの補足情報です。足したいときは、toArray の中に入れます。

php
/**
 * リソースを配列に変える
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'data' => $this->collection,
        'links' => [
            'self' => 'link-value',
        ],
    ];
}

ページ送りのレスポンスには、Laravel が links と meta を自動で足します。自分で足した links は、ページ送りの links と合わさるので、うっかり上書きしてしまう心配はありません。

いちばん外側にだけ付けるメタ情報#

いちばん外側のリソースのときだけ、メタ情報を付けたいことがあります。レスポンス全体の情報などです。リソースのクラスに with メソッドを足し、メタ情報の配列を返します。いちばん外側のときだけ、レスポンスに入ります。

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    /**
     * リソースコレクションを配列に変える
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return parent::toArray($request);
    }

    /**
     * リソースの配列といっしょに返す、追加のデータ
     *
     * @return array<string, mixed>
     */
    public function with(Request $request): array
    {
        return [
            'meta' => [
                'key' => 'value',
            ],
        ];
    }
}

リソースを作るときにメタ情報を足す#

ルートやコントローラーでリソースを作るときにも、いちばん外側のデータを足せます。どのリソースにも additional があり、レスポンスに足すデータの配列を渡します。

php
return User::all()
    ->load('roles')
    ->toResourceCollection()
    ->additional(['meta' => [
        'key' => 'value',
    ]]);

JSON:API のリソース#

Laravel には、JSON:API(API の返し方の、公開された決まり)に沿ったレスポンスを作る JsonApiResource があります。ふつうの JsonResource を継承しています。リソースの形・リレーション・項目の絞り込み・含める関連の指定・属性をあと回しで計算すること(必要になるまで計算しない)を、自動で扱います。また、Content-Type ヘッダー(中身の種類をブラウザなどに伝える情報)を application/vnd.api+json にします。

補足

Laravel の JSON:API リソースは、レスポンスを作る側の機能です。届いた JSON:API のクエリ(絞り込みや並べ替え)を読む側も必要なら、Spatie の Laravel Query Builder というパッケージと組み合わせて使えます。

JSON:API のリソースを作る#

make:resource に --json-api を付けます。

bash
php artisan make:resource PostResource --json-api

できたクラスは Illuminate\Http\Resources\JsonApi\JsonApiResource を継承し、$attributes と $relationships の2つのプロパティを持ちます。ここに書いていきます。

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\JsonApi\JsonApiResource;

class PostResource extends JsonApiResource
{
    /**
     * リソースの属性
     */
    public $attributes = [
        // ...
    ];

    /**
     * リソースのリレーション
     */
    public $relationships = [
        // ...
    ];
}

ふつうのリソースと同じく、ルートやコントローラーから返せます。

php
use App\Http\Resources\PostResource;
use App\Models\Post;

Route::get('/api/posts/{post}', function (Post $post) {
    return new PostResource($post);
});

モデルの toResource でも返せます。

php
Route::get('/api/posts/{post}', function (Post $post) {
    return $post->toResource();
});

JSON:API に沿ったレスポンスになります。

json
{
    "data": {
        "id": "1",
        "type": "posts",
        "attributes": {
            "title": "Hello World",
            "body": "This is my first post."
        }
    }
}

JSON:API のリソースの集まりを返すには、collection か、toResourceCollection を使います。

php
return PostResource::collection(Post::all());

return Post::all()->toResourceCollection();

属性を決める#

JSON:API のリソースに入れる属性を決める方法は2つあります。

いちばん簡単なのは、$attributes プロパティに、属性の名前を並べる方法です。元のモデルから、そのまま読み取られます。

php
public $attributes = [
    'title',
    'body',
    'created_at',
];

計算が重い属性は、toAttributes からクロージャで返すと、レスポンスで本当に必要になったときだけ計算されます。

属性を自分で細かく決めたいなら、リソースの toAttributes メソッドを上書きします(自分のクラスで書き直します)。

php
/**
 * リソースの属性を取り出す
 *
 * @return array<string, mixed>
 */
public function toAttributes(Request $request): array
{
    return [
        'title' => $this->title,
        'body' => $this->body,
        'is_published' => fn () => $this->published_at !== null,
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

リレーションを決める#

JSON:API のリソースでは、JSON:API の決まりに沿ったリレーションを定義できます。リレーションは、クライアント(呼び出す側)が include というクエリパラメータ(URL の ? 以降に付ける指定)で求めたときだけ、返されます。

$relationships プロパティ#

リソースに含められるリレーションを、$relationships プロパティに並べます。

php
public $relationships = [
    'author',
    'comments',
];

リレーションの名前を並べると、Laravel は対応する Eloquent のリレーションを調べ、使うリソースのクラスも自動で見つけます。リソースのクラスを自分で決めたいときは、キーとクラスの組で書きます。

php
use App\Http\Resources\UserResource;

public $relationships = [
    'author' => UserResource::class,
    'comments',
];

toRelationships メソッドを上書きして書くこともできます。

php
/**
 * リソースのリレーションを取り出す
 */
public function toRelationships(Request $request): array
{
    return [
        'author' => UserResource::class,
        'comments' => fn () => CommentResource::collection(
            $request->user()->is($this->resource)
                ? $this->comments
                : $this->comments->where('is_public', true),
        ),
    ];
}

クロージャを使うと、リレーションの中身をより細かく決められます。それでも、リレーションが読み込まれるのは、クライアントが求めたときだけです。

リレーションを含める#

クライアントは、include で、関連するリソースを求められます。

text
GET /api/posts/1?include=author,comments

relationships に、リソースを指す小さな情報(resource identifier object)が入り、いちばん上の included の配列に、リソースそのものが入ります。

json
{
    "data": {
        "id": "1",
        "type": "posts",
        "attributes": {
            "title": "Hello World"
        },
        "relationships": {
            "author": {
                "data": {
                    "id": "1",
                    "type": "users"
                }
            },
            "comments": {
                "data": [
                    {
                        "id": "1",
                        "type": "comments"
                    }
                ]
            }
        }
    },
    "included": [
        {
            "id": "1",
            "type": "users",
            "attributes": {
                "name": "Taylor Otwell"
            }
        },
        {
            "id": "1",
            "type": "comments",
            "attributes": {
                "body": "Great post!"
            }
        }
    ]
}

入れ子のリレーションは、ドット(.)で含められます。

text
GET /api/posts/1?include=comments.author

リレーションの深さ#

入れ子のリレーションを含められる深さには、最初から上限があります。上限は maxRelationshipDepth で変えられます。アプリのサービスプロバイダのどこかで呼ぶのがふつうです。

php
use Illuminate\Http\Resources\JsonApi\JsonApiResource;

JsonApiResource::maxRelationshipDepth(3);

種類(type)と ID#

リソースの type(種類)は、リソースのクラス名から決まります。たとえば、PostResource なら posts、BlogPostResource なら blog_posts です。id は、モデルの主キーから決まります。

変えたいときは、toType と toId を上書きします。

php
/**
 * リソースの type を取り出す
 */
public function toType(Request $request): string
{
    return 'articles';
}

/**
 * リソースの ID を取り出す
 */
public function toId(Request $request): string
{
    return (string) $this->uuid;
}

type とクラス名を変えたいときに役立ちます。たとえば、AuthorResource が User モデルを包んでいて、type を authors にしたい場合です。

項目の絞り込みと含める関連#

JSON:API のリソースは、sparse fieldsets(必要な項目だけを求める方法)に対応しています。クライアントは、fields というクエリパラメータで、リソースの種類ごとに、ほしい属性だけを求められます。

text
GET /api/posts?fields[posts]=title,created_at&fields[users]=name

この例では、posts のリソースには title と created_at だけが、users のリソースには name だけが入ります。

クエリ文字列を無視する#

あるレスポンスで、項目の絞り込みを使わせたくないときは、ignoreFieldsAndIncludesInQueryString を呼びます。

php
return $post->toResource()
    ->ignoreFieldsAndIncludesInQueryString();

読み込み済みのリレーションを含める#

ふつう、リレーションは、include で求められたときだけ入ります。先読み(eager loading)してあるリレーションを、クエリ文字列に関係なくすべて入れたいときは、includePreviouslyLoadedRelationships を呼びます。

php
return $post->load('author', 'comments')
    ->toResource()
    ->includePreviouslyLoadedRelationships();

リンクとメタ情報#

JSON:API のリソースに、リンクとメタ情報を足すには、toLinks と toMeta を上書きします。

php
/**
 * リソースのリンクを取り出す
 */
public function toLinks(Request $request): array
{
    return [
        'self' => route('api.posts.show', $this->resource),
    ];
}

/**
 * リソースのメタ情報を取り出す
 */
public function toMeta(Request $request): array
{
    return [
        'readable_created_at' => $this->created_at->diffForHumans(),
    ];
}

レスポンスの、リソースのデータに、links と meta が入ります。

json
{
    "data": {
        "id": "1",
        "type": "posts",
        "attributes": {
            "title": "Hello World"
        },
        "links": {
            "self": "https://example.com/api/posts/1"
        },
        "meta": {
            "readable_created_at": "2 hours ago"
        }
    }
}

JSON:API のメソッドの一覧#

JSON:API のリソースで使えるプロパティとメソッドを、まとめます。

名前 説明
$attributes 返す属性の名前を並べるプロパティ
toAttributes 返す属性を、自分で細かく決めるメソッド
$relationships 含められるリレーションを並べるプロパティ
toRelationships 含められるリレーションを、自分で細かく決めるメソッド
toType リソースの type を決めるメソッド
toId リソースの id を決めるメソッド
toLinks リソースの links を決めるメソッド
toMeta リソースの meta を決めるメソッド
maxRelationshipDepth 入れ子のリレーションを含められる深さの上限を決める
ignoreFieldsAndIncludesInQueryString 項目の絞り込み(sparse fieldset)を、このレスポンスでは使わない
includePreviouslyLoadedRelationships 読み込み済みのリレーションを、すべて含める

リソースのレスポンスを調整する#

ここまで見たとおり、リソースは、ルートやコントローラーから、そのまま返せます。

php
use App\Models\User;

Route::get('/user/{id}', function (string $id) {
    return User::findOrFail($id)->toResource();
});

ただ、クライアントに送る前に、HTTP レスポンスそのものを調整したいことがあります。方法は2つあります。

1つ目は、リソースに response をつなげる方法です。Illuminate\Http\JsonResponse が返るので、ヘッダーを自由に決められます。

php
use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/user', function () {
    return User::find(1)
        ->toResource()
        ->response()
        ->header('X-Value', 'True');
});

2つ目は、リソースの中に withResponse メソッドを定義する方法です。そのリソースが、いちばん外側のリソースとして返されるときに呼ばれます。

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    /**
     * リソースを配列に変える
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
        ];
    }

    /**
     * リソースの、送り出すレスポンスを調整する
     */
    public function withResponse(Request $request, JsonResponse $response): void
    {
        $response->header('X-Value', 'True');
    }
}

関連するページ#

公式ドキュメント(英語)

2026年10月5日時点の内容をもとに、日本語でまとめています。

ページの一覧