php elephant sticker
Photo by RealToughCandy.com on Pexels.com
目次

【実務完全ガイド】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);
}

これで datalinksmeta が含まれた構造になります。
一覧と詳細で同じ 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 モデルをそのまま返すと、最初は楽です。ですが、次のような問題が起こりやすいです。

  • hiddenvisible に 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 化するところから始めると、設計の違いを実感しやすいです。


参考リンク

投稿者 greeden

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)