API リソース
モデルを API 向けの JSON に作り変える「API リソース」の作り方と、条件つきの項目・ページ送り・メタ情報・JSON:API 形式・レスポンスの調整を説明します。
API(ほかのプログラムにデータを渡す窓口)を作るとき、データベースのモデルをそのまま外へ出したくないことがあります。たとえば、ある人にだけ見せたい項目があったり、関連するデータをいつも付けたかったりする場合です。API リソースは、モデルと、実際に返す JSON(データを文字で表す形式)のあいだに入って、「どの項目をどんな形で返すか」を決めるクラスです。ふつうに toJson で変えることもできますが、リソースを使うと、より細かく安全に決められます。
リソースを作る#
make:resource という Artisan コマンド(php artisan で動かす Laravel のコマンド)で、リソースのクラスを作れます。app/Http/Resources に置かれ、Illuminate\Http\Resources\Json\JsonResource を継承します(このクラスをもとにして作られます)。
php artisan make:resource UserResource
リソースコレクション#
モデル1つではなく、モデルの集まりを作り変えるリソースコレクションも作れます。集まり全体に関わるリンクやメタ情報(データについての補足)を JSON に入れたいときに便利です。
作るときに --collection を付けるか、名前に Collection を入れます。Illuminate\Http\Resources\Json\ResourceCollection を継承します。
php artisan make:resource User --collection
php artisan make:resource UserCollection
全体のイメージ#
補足
ここは、リソースとリソースコレクションの大まかな説明です。あとの節も読むと、細かい調整のしかたが分かります。
リソースのクラスは、「1つのモデルを、どんな JSON にするか」を表します。たとえば、簡単な UserResource は次のとおりです。
<?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 から、モデルのプロパティを直接使えます。リソースが、プロパティやメソッドへのアクセスを、中のモデルに引き渡してくれるからです。定義したら、ルートやコントローラーから返せます。リソースを作るときに、モデルを渡します。
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/user/{id}', function (string $id) {
return new UserResource(User::findOrFail($id));
});
モデルの toResource メソッドを使うと、Laravel の決まりに従って、リソースを自動で見つけてくれます。
return User::findOrFail($id)->toResource();
toResource は、モデルと同じ名前(うしろに Resource が付いていてもよい)のリソースを、モデルの名前空間(App\Models のような、クラスの住所)にいちばん近い Http\Resources から探します。
名前や置き場所が決まりと合わないときは、モデルに UseResource 属性(クラスの前に書く印)を付けて、既定のリソースを指定できます。
<?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 にクラスを渡して指定することもできます。
return User::findOrFail($id)->toResource(CustomUserResource::class);
リソースコレクションの使い方#
リソースの集まりや、ページ送りの結果を返すときは、リソースクラスの collection メソッドを使います。
use App\Http\Resources\UserResource;
use App\Models\User;
Route::get('/users', function () {
return UserResource::collection(User::all());
});
Eloquent のコレクションの toResourceCollection メソッドを使うと、リソースコレクションを自動で見つけてくれます。
return User::all()->toResourceCollection();
toResourceCollection は、モデルと同じ名前のうしろに Collection が付いたリソースコレクションを、モデルの名前空間にいちばん近い Http\Resources から探します。
名前や置き場所が決まりと合わないときは、UseResourceCollection 属性で指定します。
<?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 にクラスを渡しても指定できます。
return User::all()->toResourceCollection(CustomUserCollection::class);
専用のリソースコレクション#
ふつうのリソースコレクションには、返すデータに決まったメタ情報を足せません。足したいときは、専用のリソースコレクションを作ります。
php artisan make:resource UserCollection
作ったクラスに、レスポンスに入れたいメタ情報を書けます。
<?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',
],
];
}
}
ルートやコントローラーから返せます。
use App\Http\Resources\UserCollection;
use App\Models\User;
Route::get('/users', function () {
return new UserCollection(User::all());
});
ここでも、toResourceCollection で自動で見つけさせられます。
return User::all()->toResourceCollection();
この場合も、モデルと同じ名前のうしろに Collection が付いたクラスを、Http\Resources から探します。
コレクションのキーを残す#
ルートからリソースコレクションを返すと、Laravel はコレクションのキーを 0, 1, 2… の順に振り直します。元のキーを残したいときは、リソースのクラスに PreserveKeys 属性を付けます。
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Attributes\PreserveKeys;
use Illuminate\Http\Resources\Json\JsonResource;
#[PreserveKeys]
class UserResource extends JsonResource
{
// ...
}
preserveKeys が true になっていると、ルートやコントローラーから返したときに、キーが残ります。
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
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
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,
];
}
}
定義したら、ルートやコントローラーから、そのまま返せます。
use App\Models\User;
Route::get('/user/{id}', function (string $id) {
return User::findOrFail($id)->toResource();
});
リレーションを入れる#
関連するリソース(リレーション)も、レスポンスに入れられます。toArray の配列に足します。次の例は、PostResource の collection を使って、ユーザーの記事を入れます。
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」(臨時)のリソースコレクションを作れます。
use App\Models\User;
Route::get('/users', function () {
return User::all()->toResourceCollection();
});
ただし、集まりといっしょに返すメタ情報を変えたいなら、専用のリソースコレクションを定義する必要があります。
<?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つ用のリソースと同じく、ルートやコントローラーから、そのまま返せます。
use App\Http\Resources\UserCollection;
use App\Models\User;
Route::get('/users', function () {
return new UserCollection(User::all());
});
ここでも toResourceCollection で自動で見つけさせられます。
return User::all()->toResourceCollection();
データを data で包む(Data Wrapping)#
いちばん外側のリソースは、JSON にするとき、ふつう data というキーで包まれます。たとえば、リソースコレクションのレスポンスは、次のようになります。
{
"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
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
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 が必ず入るからです。
{
"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 や、専用のリソースコレクションに渡せます。
use App\Http\Resources\UserCollection;
use App\Models\User;
Route::get('/users', function () {
return new UserCollection(User::paginate());
});
ペジネータの toResourceCollection でも、リソースコレクションを自動で見つけられます。
return User::paginate()->toResourceCollection();
ページ送りのレスポンスには、ページの状態を表す meta と links が必ず入ります。
{
"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 を受け取ります。
/**
* ページ送りの情報を変える
*
* @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 の例です。
/**
* リソースを配列に変える
*
* @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 のときだけ、値を計算します。
'secret' => $this->when($request->user()->isAdmin(), function () {
return 'secret-value';
}),
whenHas は、元のモデルに、その属性が実際にあるときだけ入れます。
'name' => $this->whenHas('name'),
whenNotNull は、値が null ではないときだけ入れます。
'name' => $this->whenNotNull($this->name),
条件つきの項目をまとめて入れる#
同じ条件で入れる項目がいくつもあるときは、mergeWhen を使います。
/**
* リソースを配列に変える
*
* @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 を使います。むだに読み込まないよう、リレーションそのものではなく、リレーションの名前を渡します。
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 は送る前に取り除かれます。
条件つきの件数#
リレーションの「件数」も、読み込まれているときだけ入れられます。
new UserResource($user->loadCount('posts'));
whenCounted は、リレーションの件数が読み込まれているときだけ、項目を入れます。
/**
* リソースを配列に変える
*
* @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 で、読み込まれているときだけ入れられます。
'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引数は、情報があるときに返す値を作るクロージャです。
/**
* リソースを配列に変える
*
* @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引数に、その中間モデルのインスタンス(クラスから作った実物)を渡せます(リレーションを見てください)。
'expires_at' => $this->whenPivotLoaded(new Membership, function () {
return $this->pivot->expires_at;
}),
中間の表の名前を pivot 以外に変えているなら、whenPivotLoadedAs を使います。
/**
* リソースを配列に変える
*
* @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 の中に入れます。
/**
* リソースを配列に変える
*
* @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
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 があり、レスポンスに足すデータの配列を渡します。
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 を付けます。
php artisan make:resource PostResource --json-api
できたクラスは Illuminate\Http\Resources\JsonApi\JsonApiResource を継承し、$attributes と $relationships の2つのプロパティを持ちます。ここに書いていきます。
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\JsonApi\JsonApiResource;
class PostResource extends JsonApiResource
{
/**
* リソースの属性
*/
public $attributes = [
// ...
];
/**
* リソースのリレーション
*/
public $relationships = [
// ...
];
}
ふつうのリソースと同じく、ルートやコントローラーから返せます。
use App\Http\Resources\PostResource;
use App\Models\Post;
Route::get('/api/posts/{post}', function (Post $post) {
return new PostResource($post);
});
モデルの toResource でも返せます。
Route::get('/api/posts/{post}', function (Post $post) {
return $post->toResource();
});
JSON:API に沿ったレスポンスになります。
{
"data": {
"id": "1",
"type": "posts",
"attributes": {
"title": "Hello World",
"body": "This is my first post."
}
}
}
JSON:API のリソースの集まりを返すには、collection か、toResourceCollection を使います。
return PostResource::collection(Post::all());
return Post::all()->toResourceCollection();
属性を決める#
JSON:API のリソースに入れる属性を決める方法は2つあります。
いちばん簡単なのは、$attributes プロパティに、属性の名前を並べる方法です。元のモデルから、そのまま読み取られます。
public $attributes = [
'title',
'body',
'created_at',
];
計算が重い属性は、toAttributes からクロージャで返すと、レスポンスで本当に必要になったときだけ計算されます。
属性を自分で細かく決めたいなら、リソースの toAttributes メソッドを上書きします(自分のクラスで書き直します)。
/**
* リソースの属性を取り出す
*
* @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 プロパティに並べます。
public $relationships = [
'author',
'comments',
];
リレーションの名前を並べると、Laravel は対応する Eloquent のリレーションを調べ、使うリソースのクラスも自動で見つけます。リソースのクラスを自分で決めたいときは、キーとクラスの組で書きます。
use App\Http\Resources\UserResource;
public $relationships = [
'author' => UserResource::class,
'comments',
];
toRelationships メソッドを上書きして書くこともできます。
/**
* リソースのリレーションを取り出す
*/
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 で、関連するリソースを求められます。
GET /api/posts/1?include=author,comments
relationships に、リソースを指す小さな情報(resource identifier object)が入り、いちばん上の included の配列に、リソースそのものが入ります。
{
"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!"
}
}
]
}
入れ子のリレーションは、ドット(.)で含められます。
GET /api/posts/1?include=comments.author
リレーションの深さ#
入れ子のリレーションを含められる深さには、最初から上限があります。上限は maxRelationshipDepth で変えられます。アプリのサービスプロバイダのどこかで呼ぶのがふつうです。
use Illuminate\Http\Resources\JsonApi\JsonApiResource;
JsonApiResource::maxRelationshipDepth(3);
種類(type)と ID#
リソースの type(種類)は、リソースのクラス名から決まります。たとえば、PostResource なら posts、BlogPostResource なら blog_posts です。id は、モデルの主キーから決まります。
変えたいときは、toType と toId を上書きします。
/**
* リソースの 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 というクエリパラメータで、リソースの種類ごとに、ほしい属性だけを求められます。
GET /api/posts?fields[posts]=title,created_at&fields[users]=name
この例では、posts のリソースには title と created_at だけが、users のリソースには name だけが入ります。
クエリ文字列を無視する#
あるレスポンスで、項目の絞り込みを使わせたくないときは、ignoreFieldsAndIncludesInQueryString を呼びます。
return $post->toResource()
->ignoreFieldsAndIncludesInQueryString();
読み込み済みのリレーションを含める#
ふつう、リレーションは、include で求められたときだけ入ります。先読み(eager loading)してあるリレーションを、クエリ文字列に関係なくすべて入れたいときは、includePreviouslyLoadedRelationships を呼びます。
return $post->load('author', 'comments')
->toResource()
->includePreviouslyLoadedRelationships();
リンクとメタ情報#
JSON:API のリソースに、リンクとメタ情報を足すには、toLinks と toMeta を上書きします。
/**
* リソースのリンクを取り出す
*/
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 が入ります。
{
"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 |
読み込み済みのリレーションを、すべて含める |
リソースのレスポンスを調整する#
ここまで見たとおり、リソースは、ルートやコントローラーから、そのまま返せます。
use App\Models\User;
Route::get('/user/{id}', function (string $id) {
return User::findOrFail($id)->toResource();
});
ただ、クライアントに送る前に、HTTP レスポンスそのものを調整したいことがあります。方法は2つあります。
1つ目は、リソースに response をつなげる方法です。Illuminate\Http\JsonResponse が返るので、ヘッダーを自由に決められます。
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
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日時点の内容をもとに、日本語でまとめています。