Laravelの解説記事を見ながら手を動かして、最初の一歩でつまずくことがあります。routesディレクトリを開いてもapi.phpが無いからです。11系でアプリケーションの骨格が薄くなって以降、APIのルートファイルは新規プロジェクトに含まれなくなりました。この記事では13系(実測時点の最新タグはv13.29.0・2026年8月25日公開)を前提に、ルートを生やすところからJSONの形を固定し、エラー応答を1か所へまとめるところまでを、公式ドキュメントの記述に沿って順に組み立てます。受託開発でこの構成を選ぶ条件と、選ばないほうがよい場面も最後に置きました。
まとめ:APIを作る前に決めておく3つの分岐
先に結論から示します。Laravelでのapi構築は、手を動かす前に3つの分岐を決めておくと後戻りが起きません。
- ルートの入口をどうするか。
php artisan install:apiでapi.phpを生やすのが既定路線です。ただしこのコマンドはSanctumも同時に入れるため、認証方式を別に決めている場合は入った依存を把握しておきます - JSONの形をどこで決めるか。モデルをそのまま返すか、APIリソースクラスを挟むかの二択です。外部に公開するAPIなら挟む一択で、社内限定の管理画面用なら省いても回ります
- エラー応答を誰が決めるか。個々のコントローラで
response()->json()を書くか、bootstrap側の例外ハンドラで統一するか。エンドポイントが5本を超えるなら後者を選びます
この3点を決めずにRoute::apiResourceだけ先に書くと、後からエラー形式を揃える作業がエンドポイント数ぶん発生します。逆に、単発のWebhook受け口を1本作るだけならAPIリソースも例外ハンドラの調整も要りません。規模に対して過剰な構成を組まない判断が、この記事の主題です。
api.phpは既定で存在しない:install:apiが置くもの
7系や8系の解説は、routes/api.phpが最初からある構成を前提とした内容です。11系以降は構成が異なります。公式ドキュメントのRoutingは「ステートレスなAPIも提供するならinstall:apiコマンドでAPIルーティングを有効にできる」と書いており、このコマンドがapi.phpを作るとの明記もあります。既存記事との食い違いはここが原因です。
install:apiが作成するファイルと同時に入る認証基盤の中身
実行するコマンドは1つです。
php artisan install:api
これでroutes/api.phpが生成され、bootstrap/app.phpのwithRoutingにapi側の登録が加わる仕組みです。同時にLaravel Sanctumがインストールされ、そのマイグレーションも配置されます。ドキュメントにおけるSanctumの説明は「サードパーティのAPI利用者、SPA、モバイルアプリケーションを認証するために使えるトークン認証ガード」です。
注意したいのは、認証方式をまだ決めていない段階でもSanctumが入る点です。OAuth2のサーバを立てる予定があるなら後で構成を差し替えることになりますし、社内ネットワーク限定でIPで絞る運用なら使わないパッケージが残ります。composerの依存として残ること自体は害になりませんが、更新を追う対象が1つ増えるという意味では把握しておくべきです。トークンの発行方法や有効期限の設計は、それ単体で扱う分量があるため本記事では踏み込みません。詳しくはLaravel Sanctumとは:SPAとモバイルのトークン認証とCookieセッションの使い分けを参照してください。
apiプレフィックスをbootstrap側で変更する手順と注意点
api.phpに書いたルートには/apiというURIプレフィックスが自動で付きます。ファイル内で毎回書く必要はありません。裏を返すと、Route::get('users', ...)と書いた場合に叩くURLは/usersではなく/api/usersです。404が返って手が止まる原因の多くはこれです。
プレフィックスを変えたい場合はbootstrap/app.phpを編集し、withRoutingにapiPrefixを渡します。ドキュメントの例ではapiPrefix: 'api/admin'を指定しています。管理系のAPIだけ別の入口にしたい要件で使う書き方です。ここを空文字にしてルート直下に出すこともできますが、web.php側のルートとURIが衝突しやすくなるため、既存の画面がある案件では避けたほうが安全でしょう。
設定の置き場所がbootstrap/app.phpへ集約されたのは11系での変更です。app/Http/Kernel.phpやRouteServiceProviderを探して見つからないときは、まずこの骨格変更を疑ってください。11系で何が動いたかはLaravel 11の変更点とサポート期限をまとめた記事で整理しています。
apiグループとwebグループの違いからセッションの扱い方を決める
api.phpのルートはステートレスで、apiミドルウェアグループが割り当てられます。web.phpとの差はセッションとCSRFです。webグループはセッションを開始しCSRFトークンを検証しますが、apiグループはどちらも行いません。
この違いが表に出るのは、SPAから同一ドメインのAPIを呼ぶときです。Cookieベースの認証を使うなら、リクエストはセッションを持ち回る必要があり、api.php側の素の設定では認証状態が維持されません。フロントを別に立てる構成での認証まわりの選択肢はLaravelとReactの連携ガイドに整理してあります。バックエンドだけ先に作って後からフロントを繋ぐ順序で進める場合、ここを最後に回すと手戻りが大きくなります。
apiResourceでCRUDのエンドポイントを1行で登録する
ルートファイルができたら、次はエンドポイントの登録です。CRUDを1本ずつRoute::getやRoute::postで書き並べることもできますが、リソース単位で扱う設計なら1行で済みます。
make:controllerの–apiで生成されない2つのアクション
まずコントローラを作ります。
php artisan make:controller PhotoController --api
--apiを付けると、createとeditを除いた5つのメソッドが生成されます。index・store・show・update・destroyです。除かれる2つはHTMLのフォーム画面を返すためのアクションで、JSONを返すAPIには出番がありません。--resourceを使うと7つ全部が生成されるため、APIでは--apiを選びます。生成オプションの一覧はphp artisan makeコマンドの早見表にまとめています。
ルート側にも対応する書き方があります。
Route::apiResource('photos', PhotoController::class);
この1行で登録されるルートは5本です。複数のリソースをまとめるならRoute::apiResources([...])に連想配列を渡します。プロフィールのように1件しか存在しないリソースにはRoute::apiSingletonがあり、識別子を含まないURIでshowとupdateを登録する仕組みです。登録済みのルートはphp artisan route:listで確認できます。想定と違うURIになっていないか、この時点で一度見ておくと後の作業が楽です。
ルートモデルバインディングが404を返す仕組みと応答を制御する方法
コントローラのメソッドでPhoto $photoと型宣言すると、URIの{photo}に一致するモデルが自動で解決されます。見つからなければLaravelが404を返すため、findOrFailを自分で書く必要はありません。--model=Photoを付けて生成すると、この型宣言まで含んだコントローラができあがります。
既定の挙動を変えるための入口も用意済みです。リソースルートにmissingメソッドを繋ぐと、モデルが見つからなかったときの処理をクロージャで差し替えられます。ソフトデリート済みのモデルは既定で404になりますが、withTrashed()を繋げば取得対象に含められ、引数に配列を渡せばshowだけといった限定もできます。削除済みデータを参照するAPIを別途作るより、この指定で足りる場面が多いはずです。
バージョニングをprefixで切る方式とファイルで分ける方式
Laravelには専用のAPIバージョニング機構がありません。自分で決めます。実務で使われるのは2つの方式です。
| 方式 | 書き方 | 向く場面 |
|---|---|---|
| prefixグループ | api.php内でv1グループを作る | 保守する版が2つまで |
| ファイル分割 | withRoutingでv2用を追加登録 | 保守する版が3つ以上 |
前者はRoute::prefix('v1')->group(function () { ... });で囲むだけです。コントローラもApp\Http\Controllers\Api\V1のように名前空間で分けます。後者はルートファイル自体をv1用とv2用に分け、withRoutingのusingクロージャで両方を登録する形になります。
判断の目安は、同時に保守する版の数です。2つまでなら1ファイル内のグループで見通しが利きます。3つを超えると差分の把握が難しくなるため、ファイルを分けたほうが結果として読みやすくなるでしょう。どちらを選んでも、後から移行するコストは低い部類です。最初から凝った仕組みを入れる必要はありません。
APIリソースでJSONの応答形式をモデル変更の影響から安全に切り離す
コントローラからモデルをそのままreturnすると、Laravelは自動でJSONへ変換します。動きはしますが、テーブルに列を1本足しただけでAPIの応答が変わる状態になります。外部に公開するAPIでこれは避けたい挙動です。間に変換層を挟むのがAPIリソースです。
make:resourceとtoResourceで生成と呼び出しを短くする
生成コマンドはこうです。
php artisan make:resource PhotoResource
できたクラスはapp/Http/Resourcesに置かれ、JsonResourceを継承します。toArrayメソッドの中で、返したいキーと値の対応を書きます。ここに書いていない列は応答に出ません。コレクション用のクラスが要る場合は--collectionを付けるか、クラス名にCollectionを含めます。
呼び出し側はnew PhotoResource($photo)と書くのが基本形ですが、13系では短縮形が使えます。モデルのtoResource、EloquentコレクションのtoResourceCollectionです。命名規約から対応するリソースクラスを自動で見つけます。規約から外れた名前や別の名前空間に置いた場合は、モデル側にUseResourceCollection属性を付けるか、メソッドの引数でクラスを明示します。モデル定義そのものの書き方はLaravel Eloquentの基礎をまとめた記事を参照してください。
dataキーのラッピングとページネーションのmetaとlinks
APIリソースを返すと、最外殻がdataキーに包まれます。フロント側でresponse.data.dataと二重に辿ることになるのはこの仕様のためです。外したい場合はJsonResource::withoutWrapping()をサービスプロバイダのbootで1度呼びます。ドキュメントはAppServiceProviderなど毎リクエスト読み込まれるプロバイダでの呼び出しを勧めています。
ここで判断が要ります。dataを外すと、ページネーションを返すときに困ります。ページャをリソース経由で返すと、件数や現在ページがmetaに、前後のURLがlinksに入った構造で出力されるからです。ラッピングを外すとこの入れ物ごと消える扱いになるため、一覧APIを持つなら包んだままにしておくほうが素直でしょう。フロント側の記述が1階層深くなるだけで済みます。
whenLoadedでリレーションを条件付きに出しN+1を避ける
toArrayの中でリレーションを$this->commentsのように直接参照すると、その場でクエリが飛びます。一覧で100件返せば追加のクエリが100本走ります。これを避けるのがwhenLoadedです。事前にwithで読み込まれているときだけキーを出力し、読み込まれていなければキーごと応答から消えます。
設計としては、コントローラ側でどのリレーションを読むかを決め、リソース側はwhenLoadedで受け身に書きます。詳細APIではコメントを含める、一覧APIでは含めない、といった出し分けがコントローラ1か所で完結する形です。属性についても、条件付きで出す仕組みが用意されています。管理者にだけ内部フラグを返す、といった分岐をリソース内に書けます。
13系のJsonApiResourceを選ぶ条件と見送ってよい場面
13系ではJsonApiResourceが同梱されました。make:resource PostResource --json-apiで生成でき、JSON:API仕様に沿った構造を自動で組み立てます。Content-Typeはapplication/vnd.api+jsonになり、sparse fieldsetsやincludesの処理も内蔵されています。生成されるクラスには$attributesと$relationshipsのプロパティが並び、そこに出力対象を書く形式です。
採用の条件ははっきりしています。APIの利用者が社外にいて、仕様の合意にJSON:API準拠が使える場面です。この場合、応答形式の議論を「仕様書に従う」で終えられます。逆に、自社フロントだけが叩く内部APIで選ぶ理由は薄いはずです。JSON:APIの構造はtypeとidとattributesの入れ子になり、素直な平坦JSONを期待するフロント実装には手数が増えます。標準のJsonResourceで足ります。
エラー応答をwithExceptionsでアプリ全体の1か所に集約して統一する
正常系が動いたあとに残るのがエラー応答です。ここを各コントローラで書くと形式が揃わず、フロント側にエラーハンドリングの分岐が増えていきます。11系以降、例外の扱いはbootstrap/app.phpのwithExceptionsに集まりました。
Acceptヘッダ依存の判定とshouldRenderJsonWhenでの上書き
Laravelは例外をHTMLで返すかJSONで返すかを、リクエストのAcceptヘッダから自動判定します。/api配下だからJSONになる、という判定ではありません。ここは誤解されやすい箇所です。curlでAcceptを付けずに叩くとHTMLのエラーページが返ってきて、原因の切り分けに時間を取られます。
判定を自分で決めたいならshouldRenderJsonWhenにクロージャを渡します。ドキュメントの例は$request->is('admin/*')のときに真を返し、それ以外は$request->expectsJson()に委ねる形です。同じ書き方でAPIのパス配下を常にJSONにできます。個別の例外を上書きしたい場合はrenderにクロージャを登録します。NotFoundHttpExceptionを捕まえてAPIパスのときだけ404のJSONを返す例が公式に載っており、クロージャが値を返さなければ既定の描画に戻る設計です。
バリデーション失敗時の422とフィールド別エラー配列の既定形式
入力チェックに失敗したとき、JSONを期待するリクエストには422が返り、本文にはmessageとフィールド別のerrorsが入ります。フォームリクエストを使っても$request->validate()を使ってもこの形です。フロント側はこの構造を前提に、フィールド名をキーにしてエラー文字列の配列を取り出せます。
自前でエラー形式を発明する前に、この既定形で足りるかを確かめてください。JavaScriptのライブラリの多くは422とerrorsの組み合わせを前提にした実装を持っています。ルール定義やカスタムルール、日本語メッセージの当て方はLaravelのバリデーションを扱った記事で詳しく書いています。
API公開時のレートリミッタ定義位置と429応答を設計する判断基準
公開APIには流量制限が必要です。レートリミッタはAppServiceProviderのbootでRateLimiter::forを呼んで定義します。ドキュメントの例はLimit::perMinute(60)にログイン済みならユーザーID、未ログインならIPを単位として指定したもので、毎分60回という制限です。定義したリミッタはthrottle:に名前を続けたミドルウェアとしてルートに付けます。
制限を超えたリクエストには429が自動で返ります。本文の形を変えたいならLimitにresponseを繋いでレスポンスを差し替える方式です。あわせて、例外の報告側にも制限をかけられます。withExceptionsのthrottleにLimit::perMinute(300)を返せば、外部サービスの障害でログが溢れる事態を抑えられるのです。API公開時にはこの2つを対にして設定しておくと、障害時の挙動が読みやすくなります。
受託開発でAPIを作る採用条件と見送る判断基準を要件別に整理する
ここまでの手順は「APIを作ると決めたあと」の話です。決める前の判断も書いておきます。
REST形式とAPIリソースを採用してよい3つの条件を要件から判断する
次の3つのうち2つ以上に当てはまるなら、この記事の構成をそのまま採ってかまいません。1つ目は、フロントエンドが別のリポジトリやチームで動いていること。2つ目は、モバイルアプリや社外システムなど、ブラウザ以外のクライアントが存在すること。3つ目は、データの単位がリソースとして素直に切れること。apiResourceの5アクションに載る形なら、URI設計の議論そのものを省けます。
逆に、当てはまらないのに構成だけ真似ると割に合いません。画面が10枚しかない社内システムで、フロントもバックエンドも同じ担当者が書く場合、APIを挟むぶんだけコードが増える構成です。BladeとLivewire、あるいはInertiaでモノリスのまま組んだほうが総工数は下がります。Inertiaで組む場合の仕組みと判断はLaravel Inertiaとは|APIを作らずSPAを組む仕組みと3系の変更点で扱っています。この判断を先送りにすると、リソースクラスとフロントの型定義を二重に保守する状態が続きます。
RESTを見送ってJSON-RPCなど別方式を選ぶべき業務要件の判断基準
リソースとして切れない要件もあります。「見積を再計算する」「バッチを起動する」といった、名詞ではなく動詞が主語の処理です。REST風に寄せるとPOST /estimates/{id}/recalculationのような不自然なURIが並びます。件数が少なければ許容できますが、こうした操作が処理の大半を占めるならJSON-RPCが素直です。メソッド名で呼び出す形式のため、業務処理をそのまま公開できます。Laravelでの実装はLaravel SajyaによるJSON-RPCサーバの構築手順にまとめました。
もう1つ、外部システムとの接続が主目的の場合は、そもそも自前でAPIを作らずiPaaSで繋ぐ選択肢も残ります。データを流すだけで業務ロジックを持たないなら、その方が保守対象は減るでしょう。判断が分かれるのは、既存の基幹システムと新規のWebアプリを繋ぐ案件です。認証・権限・監査ログをどちらが持つかで構成が変わるため、要件定義の段階で決めておく論点になります。API開発・システム連携では、こうした接続方式の選定から設計・実装まで請けています。
契約前に固めておくAPI仕様の4項目と関係者間での具体的な決め方
見積の精度を左右するのは、エンドポイント数ではなく次の4点です。応答形式(ラッピングの有無と日付の書式)、エラーの体系(HTTPステータスと業務エラーコードの対応)、認証方式(トークンかCookieか、有効期限と失効の扱い)、そして版の互換方針(破壊的変更をどう扱うか)。この4点が決まっていれば、エンドポイントが20本あっても実装の見通しは立ちます。
逆に、ここが空欄のまま着手すると、実装が終わったあとに応答形式の変更依頼が来ます。リソースクラスとテストとフロントの3か所を直す作業です。要件定義の成果物にサンプルJSONを1本だけでも載せておくと、この手戻りはほぼ消えます。
よくある質問
APIの構築で問い合わせの多い5点をまとめます。
install:apiを実行せずにAPIを作ることはできますか?
実現は可能です。web.phpにルートを書いてJSONを返しても動作します。ただしwebグループはセッションとCSRF検証を通すため、外部システムからのPOSTは419で弾かれる仕組みです。除外設定を書き足すことになり、結局は手間が増えるでしょう。ステートレスな入口が要るなら素直にinstall:apiを実行してください。Sanctumを使わない方針なら、後から依存を外す判断も取れます。
ルートを追加したのに404が返るのはなぜですか?
URLに/apiが抜けている可能性が高いです。api.phpのルートには既定でこのプレフィックスが付きます。php artisan route:listで登録済みのURIを確認してください。それでも合わない場合は、ルートキャッシュが古いままかもしれません。php artisan route:clearを実行してから再確認します。本番環境でキャッシュを使っているなら、デプロイ手順にキャッシュの再生成が入っているかも確認箇所になります。
APIリソースを使わずモデルをそのまま返してもよいですか?
社内限定で、フロントもバックエンドも同じチームが保守するなら許容できます。避けるべきは、外部の利用者がいるAPIです。列を追加した瞬間に応答が変わり、パスワードハッシュのような出してはいけない値がhiddenの設定漏れで露出する事故も起こります。公開範囲が社外に及ぶなら、リソースクラスを挟む手間は保険として安いほうでしょう。
APIのテストはどう書くのが標準的ですか?
機能テストでHTTPリクエストを投げ、応答のJSON構造を検証する形が基本です。ステータスコードとキーの構造を確かめるアサーションが用意されており、リソースクラスの出力が変わったときにテストが落ちます。応答形式を仕様として固定したいなら、テストにサンプルJSONの構造を書いておくのが実質的な仕様書になります。エンドポイントごとに正常系1本と異常系1本を用意するところから始めてください。
API仕様書はどうやって用意すればよいですか?
OpenAPIの定義をコードから生成するパッケージを入れる方法と、定義ファイルを手で書いて実装をそれに合わせる方法があります。フロントと並行開発するなら後者が向きます。定義が先にあれば、モックサーバを立ててフロントの実装を待たせずに進められるからです。生成型は実装が正であることを保証しやすい反面、注釈をコードに書き足す運用が必要になります。
関連記事
- LaravelとReactの連携ガイド|環境構築・SPA・認証・デプロイと選び方:ここで作ったAPIを叩く側の構成と、認証方式の選び方を扱っています
- Laravel Eloquentとは|モデル定義・リレーション・eager loadingの基礎:APIリソースが変換する元になるモデル層の解説です
- Laravelのバリデーション|validateとFormRequestの使い分け:422を返す側のルール定義とメッセージの当て方をまとめています
- Laravel Sajyaとは?JSON-RPC 2.0サーバーの実装手順とエラー処理:RESTが噛み合わない要件で選ぶ、もう一方の実装方式です