【実務完全ガイド】LaravelのAPI Resource設計――JSONレスポンス、DTO、ページネーション、エラー統一、フロント連携まで見通しよく整える方法
この記事で学べること(要点)
- Laravel の API Resource を使って、レスポンス設計を見通しよく整える考え方
- Eloquent モデルをそのまま返さず、Resource / DTO / Action で責務分離する方法
- 一覧・詳細・ネスト・条件付き項目・ページネーションの実務パターン
- フロントエンドやモバイルアプリと連携しやすい JSON の設計方針
- バリデーションエラー、認可エラー、例外時のレスポンス統一
- テストしやすく、将来の変更にも強い API の育て方
- 読み手にも実装者にもやさしい、整理された API ドキュメントにつながる設計
想定読者
- Laravel 初〜中級エンジニア:API は作れても、返却形式が画面ごとにばらついて困っている方
- テックリード:モバイルアプリやフロントエンドと連携しやすい JSON 設計を標準化したい方
- QA / 保守担当:レスポンスの構造変更で不具合が起きやすく、壊れにくい設計へ寄せたい方
- デザイナー / フロント担当:必要なデータが一貫した形で返り、画面実装や状態管理を安定させたい方
アクセシビリティレベル:★★★★☆
この記事の主題はバックエンドのレスポンス設計ですが、返すデータ構造が整うほど、フロントエンドでは見出し・状態表示・エラー案内を一貫して実装しやすくなります。結果として、色に依存しない状態表現、読み上げで理解しやすい通知、一覧や詳細の安定した表示につながります。
1. はじめに:API は「返ればよい」ではなく、「読みやすく変えやすい」が大切です
Laravel で API を作り始めたばかりの頃は、つい return User::all(); のように、Eloquent モデルをそのまま返したくなります。実際、それでも JSON は返りますし、最初の開発速度も速いです。ただ、機能が増えるにつれて、だんだん次のような悩みが出てまいります。
- 画面ごとに欲しい項目が違い、返却形式がばらつく
- モデルの内部項目までそのまま出てしまい、責務が曖昧になる
- 一覧と詳細で JSON の形が揃っておらず、フロントがつらい
- ページネーションやネストが場当たり的で、あとから整理しにくい
- 将来フィールド名を変えたいとき、影響範囲が読みにくい
- API テストが書きにくく、変更が怖い
こうした問題をやわらげるために、Laravel には API Resource があります。API Resource を使うと、「モデルの中身をそのまま見せる」のではなく、「外へどう見せるか」を明示できます。これは地味に見えて、とても大切です。レスポンスの形をコードで管理できるようになると、フロントエンドとの約束が安定し、将来の変更にも強くなります。
2. API Resource とは何か:モデルとレスポンスの間に“見せ方”を置く仕組みです
API Resource は、Laravel における JSON レスポンス専用の整形層です。
役割を素朴に言うと、次の通りです。
- Model:データそのもの
- Action / Service:業務処理
- Resource:外へ見せる形へ整える
- Controller:HTTP の入口と出口を調整する
この分け方ができると、モデルはデータとリレーションに集中できますし、API の見せ方は Resource に集約できます。たとえば User モデルに「API 用の表示名」「詳細画面専用の構造」「管理画面用の JSON」まで全部押し込む必要がなくなります。
Laravel の API Resource は、単一モデル向けの JsonResource と、一覧向けの Resource Collection の考え方で組み立てられます。最初は少し回りくどく見えるかもしれませんが、画面が増えるほどこの一手間が効いてきます。
3. まずは基本形:単一リソースを素直に作ります
例として、投稿データを返す API を考えます。
まず Resource を作成します。
php artisan make:resource PostResource
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class PostResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => (string) $this->id,
'title' => $this->title,
'slug' => $this->slug,
'excerpt' => $this->excerpt,
'published_at' => optional($this->published_at)?->toIso8601String(),
'author_name' => $this->user?->name,
];
}
}
コントローラ側では、次のように返します。
use App\Http\Resources\PostResource;
use App\Models\Post;
public function show(Post $post): PostResource
{
$post->loadMissing('user');
return new PostResource($post);
}
この時点で、すでに大きな利点があります。
idを文字列で返す、といったポリシーを統一できる- 日付形式を Resource 側で揃えられる
- モデルに存在しても、外へ出したくない項目は出さないで済む
author_nameのように、画面で扱いやすい形へ寄せられる
Eloquent をそのまま返すより、API の契約がぐっと明確になります。
4. Resource を入れるとなぜ保守しやすいのか:レスポンスの責務が集まるからです
実務では、この「どこで整形するか」がとても重要です。
もし Resource を使わないと、次のような整形がいろいろな場所へ散ります。
- コントローラで
->map()する - モデルに accessor を増やす
- サービスで配列を組み立てる
- 画面ごとに返却形式が違う
こうなると、API の形を変えたいときに、どこを直せばよいか分かりにくくなります。Resource を導入すると、「外へ出る形」は Resource を見れば分かる状態にできます。これはレビューにも効きますし、フロント担当との会話でも役立ちます。
たとえば「投稿一覧では本文全文は要らない」「詳細ではタグや著者情報も欲しい」といった話が出たときに、Resource を分けるのか、条件付き項目にするのかが整理しやすくなります。
つまり Resource は、単なる配列整形ではなく、API の設計図として機能してくれます。
5. 一覧は Resource Collection で考える:個別と一覧の責務を分けます
一覧 API では、単一データと違って、次の情報も必要になります。
- データの配列
- 件数
- ページ番号
- 次ページの有無
- フィルタ条件の保持
Laravel では、ページネーション済みの結果に PostResource::collection($posts) を使うだけでも十分整います。
use App\Http\Resources\PostResource;
use App\Models\Post;
public function index(): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
{
$posts = Post::query()
->select(['id', 'user_id', 'title', 'slug', 'excerpt', 'published_at'])
->with(['user:id,name'])
->published()
->latest()
->paginate(20);
return PostResource::collection($posts);
}
これで data、links、meta が含まれた構造になります。
一覧と詳細で同じ Resource を使えることも多いですが、実務では「一覧専用 Resource」と「詳細専用 Resource」を分けた方が分かりやすい場面も多いです。
たとえば、一覧では軽く、詳細では重く、という設計です。
5.1 一覧専用 Resource を分ける例
class PostListResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => (string) $this->id,
'title' => $this->title,
'slug' => $this->slug,
'excerpt' => $this->excerpt,
'author_name' => $this->user?->name,
'published_at' => optional($this->published_at)?->toDateString(),
];
}
}
このようにすると、一覧に不要なネストや大きな本文を返さずに済みます。
6. ネストの扱い:関連モデルをそのまま返さず、Resource を重ねます
API が育ってくると、関連データも一緒に返したくなります。ここでありがちなのが、関連モデルをそのまま配列へ入れてしまうことです。ですが、それをやると、結局モデル露出が戻ってきます。そこで、関連にも Resource を使うときれいです。
class UserSummaryResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => (string) $this->id,
'name' => $this->name,
];
}
}
class PostDetailResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => (string) $this->id,
'title' => $this->title,
'slug' => $this->slug,
'body' => $this->body,
'published_at' => optional($this->published_at)?->toIso8601String(),
'author' => new UserSummaryResource($this->whenLoaded('user')),
];
}
}
ここで whenLoaded() を使うのが実務ではとても重要です。
これにより、コントローラ側でロードしていない関連を無理に触らず、N+1 問題の再発を避けやすくなります。
7. 条件付き項目:管理者だけ見える値、詳細だけ出す値を整理します
API では、だれに対して何を見せるかが重要です。
たとえば管理者だけ内部メモを返したい、詳細画面だけコメント件数を返したい、といった場面があります。こうした条件は Resource 側で扱えます。
return [
'id' => (string) $this->id,
'title' => $this->title,
'internal_note' => $this->when(
$request->user()?->can('viewInternalNote', $this->resource),
$this->internal_note
),
];
このようにしておくと、「API の見せ方」と「権限」の境界が明確になります。
ただし、重要なのは、最終防衛は Policy や authorize 側に置くことです。
Resource の条件分岐は表示制御であって、権限制御そのものではありません。
ここを混同しないようにすると安全です。
8. DTO と Resource の使い分け:Resource は“外向け”、DTO は“中間整理”です
実務でよく迷うのが、DTO と Resource をどう使い分けるかです。
おすすめの整理は、次のようなものです。
- DTO:アプリ内部でデータをまとめる
- Resource:HTTP レスポンスとして外へ見せる
たとえば、複数モデルをまたいで集計した結果をいったん DTO にまとめ、その DTO を Resource で返す、という流れはとても自然です。
8.1 DTO の例
namespace App\Data;
class SalesSummaryData
{
public function __construct(
public readonly int $ordersCount,
public readonly int $customersCount,
public readonly int $salesTotal,
) {}
}
8.2 Service 側
class DashboardSummaryService
{
public function getTodaySummary(): SalesSummaryData
{
return new SalesSummaryData(
ordersCount: Order::whereDate('created_at', today())->count(),
customersCount: User::whereDate('created_at', today())->count(),
salesTotal: Order::whereDate('created_at', today())->sum('total_amount'),
);
}
}
8.3 Resource 側
class SalesSummaryResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'orders_count' => $this->ordersCount,
'customers_count' => $this->customersCount,
'sales_total' => $this->salesTotal,
];
}
}
このようにすると、内部処理と外部レスポンスが分離され、保守しやすくなります。
9. Eloquent をそのまま返さない理由:隠したい情報と変えたい自由を守るためです
Eloquent モデルをそのまま返すと、最初は楽です。ですが、次のような問題が起こりやすいです。
hiddenやvisibleに API 都合が入り込みすぎる- モデル内部の変更が API に波及しやすい
- 一覧・詳細・管理画面・モバイルで欲しい形が違うのに対応しづらい
- 将来フィールド名や構造を変える自由が減る
Resource を通すと、モデル内部は変えても、API 契約は保ちやすくなります。
これは、アプリが小さいうちは見えにくい利点ですが、長く運用すると本当に効いてまいります。
API は利用側との約束ですので、Eloquent の構造と一体化させすぎない方が結果的に安全です。
10. ページネーション設計:フロントが扱いやすい形を揃えます
Laravel のページネーションは便利ですが、レスポンスの意味を理解しておかないと、フロント側で扱いづらくなることがあります。
実務では、次の点を揃えておくと分かりやすいです。
data:実データmeta:総件数、現在ページ、最終ページlinks:次へ / 前へ など- フィルタ条件はクエリ文字列で保持
Laravel の Resource Collection でかなり整いますが、必要なら additional() で補足情報も付けられます。
return PostListResource::collection($posts)->additional([
'filters' => [
'q' => request('q'),
'status' => request('status'),
],
]);
このように「今どんな条件で取得された結果か」を返しておくと、フロント側の状態管理が安定しやすいです。
特にアクセシビリティの面でも、一覧件数や現在条件を画面へ出しやすくなります。
11. エラーレスポンス:成功時だけでなく、失敗時の形も統一します
API は成功時の形だけ整っていても不十分です。
バリデーションエラー、認証失敗、認可失敗、例外時の形も揃っている方が、フロント側は圧倒的に扱いやすくなります。
たとえばバリデーションエラーなら、次のような形が扱いやすいです。
{
"message": "入力内容を確認してください。",
"errors": {
"email": [
"メールアドレスは必須です。"
]
}
}
Laravel は標準でも似た形を返してくれますが、チームでどのくらい統一するかを決めておくと安心です。
さらに、認可エラーや存在しないデータの扱いも、メッセージやコードを揃えておくと、フロント側で色に依存しないエラー表示を実装しやすくなります。
画面側では、このような整ったレスポンスがあるほど、
- エラーサマリ
- 各入力との紐付け
role="alert"による通知
を安定して実装できます。
つまり、アクセシブルな UI は、バックエンドのエラーレスポンス設計とも深くつながっています。
12. リソースとパフォーマンス:N+1 を避ける前提で使います
Resource は便利ですが、使い方を誤ると裏側で余計なクエリが発生することがあります。
特に注意したいのが、toArray() の中で関連を何気なく触ってしまうケースです。
悪い例:
'author_name' => $this->user->name,
これを一覧で大量に返すと、user をロードしていなければ N+1 が起きます。
ですので、実務では次のような習慣が大切です。
- コントローラ側で
with()する - Resource 側では
whenLoaded()を使う - 一覧と詳細で必要な関連を分ける
withCount()で件数を事前に持つ
Resource はレスポンス整形の道具であって、クエリ最適化の代わりではありません。
Eloquent 設計とセットで使うと強いです。
13. 命名ルール:Resource 名は“画面や用途”で考えると分かりやすいです
Resource 名が曖昧だと、あとで困ります。
おすすめは、用途を名前へ出すことです。
UserResource:基本形UserListResource:一覧用UserDetailResource:詳細用UserSummaryResource:ネスト用AdminUserResource:管理画面向け
すべてを UserResource ひとつでやろうとすると、条件分岐だらけになりがちです。
一覧・詳細・ネストのように責務が分かれるなら、素直に分けた方が読みやすいです。
これは Blade コンポーネントの命名と似ていて、「何のためのリソースか」が分かる方が保守しやすいです。
14. ドキュメントしやすい API にする:Resource は仕様の見える化にも役立ちます
API Resource を導入すると、レスポンスの構造がコード上にはっきり現れます。
これは、そのまま API ドキュメントやレビューにも役立ちます。
たとえば OpenAPI までまだ整っていないプロジェクトでも、「この API はどんな JSON を返すのか」を Resource を見れば把握しやすくなります。
さらに、フロントエンドとの会話でも次のように進めやすくなります。
- 一覧ではこの項目だけ返します
- 詳細ではこのネストが入ります
- 件数は
meta.totalにあります - エラー時は
errors.email[]を使ってください
このような約束が見える化されると、仕様変更の話もしやすくなります。
Resource は、実装者だけでなくチーム全体の会話コストも下げてくれます。
15. テスト:Resource を使うとレスポンス構造を守りやすくなります
API Resource を入れたら、レスポンスの構造をテストで守る価値が高まります。
たとえば一覧 API のテストなら、次のように書けます。
public function test_posts_index_returns_expected_structure()
{
$user = User::factory()->create();
Post::factory()->count(3)->for($user)->create();
$response = $this->getJson('/api/posts');
$response->assertOk()
->assertJsonStructure([
'data' => [
'*' => [
'id',
'title',
'slug',
'excerpt',
'author_name',
'published_at',
],
],
'links',
'meta',
]);
}
詳細 API ならネストも確認できます。
public function test_post_detail_returns_author()
{
$post = Post::factory()
->for(User::factory(), 'user')
->create();
$response = $this->getJson("/api/posts/{$post->id}");
$response->assertOk()
->assertJsonStructure([
'data' => [
'id',
'title',
'slug',
'body',
'published_at',
'author' => [
'id',
'name',
],
],
]);
}
こうしたテストがあると、Resource を変更したときの影響が見えやすくなります。
特にフロント連携がある API では、レスポンス構造をテストで固定しておく価値がとても高いです。
16. よくある落とし穴と回避策
16.1 Resource を使わず、コントローラごとに配列を組んでしまう
最初は楽ですが、返却形式が散らばります。
できるだけ Resource へ集約した方が後で整理しやすいです。
16.2 一覧も詳細も同じ Resource ひとつで無理に済ませる
条件分岐が増えすぎるなら、一覧用と詳細用に分けた方が読みやすいです。
16.3 関連をそのまま返して N+1 が起きる
with()、withCount()、whenLoaded() をセットで意識すると防ぎやすいです。
16.4 モデルの accessor に API 都合を入れすぎる
表示用の整形と API 用の整形は違うことが多いです。
外向けの形は Resource に寄せた方が責務が明確です。
16.5 エラーレスポンスだけ整っていない
成功時と同じくらい、失敗時の形も大切です。
フロント実装やアクセシビリティの質に直結します。
17. チェックリスト(配布用)
設計
- [ ] Eloquent モデルをそのまま返していない
- [ ] Resource にレスポンス設計を集約している
- [ ] 一覧 / 詳細 / ネストで責務を分けている
- [ ] DTO と Resource の役割が整理されている
パフォーマンス
- [ ] Resource で触る関連は
with()/whenLoaded()前提になっている - [ ] 件数は
withCount()を使っている - [ ] 一覧は必要列だけ返している
レスポンス品質
- [ ] ページネーション構造が統一されている
- [ ] 条件付き項目の出し方が整理されている
- [ ] エラーレスポンスの形が揃っている
テスト
- [ ] 一覧 API の構造テストがある
- [ ] 詳細 API のネスト構造テストがある
- [ ] エラー時の JSON 構造も確認している
フロント連携 / アクセシビリティ
- [ ] 状態や件数がフロントで表示しやすい形になっている
- [ ] 色に依存しないラベル設計へつなげやすい
- [ ] 入力エラーがフィールドへ結びつけやすい JSON になっている
18. まとめ
Laravel の API Resource は、単なる JSON 整形の道具ではありません。
モデルとレスポンスの間に「見せ方の層」を置くことで、API の契約が明確になり、フロントとの連携、保守、テスト、将来の変更がずっと楽になります。
一覧と詳細を分ける、関連も Resource で整える、DTO は内部整理に使う、エラーレスポンスも揃える。こうした積み重ねが、読みやすく壊れにくい API へつながります。
最初から全部を完璧にしなくても大丈夫です。まずは「モデルをそのまま返している1本の API」を Resource 化するところから始めると、設計の違いを実感しやすいです。
