はじめに — 何を作り、何を作らないか
Laravel でエンドポイントを 1 本書けることと、その API を本番で運用できることは別の技術です。
チュートリアルどおりに書いたコードは、手元で叩くかぎり正しく動きます。壊れるのは、複数のリクエストが同時に届いたとき、外部サービスが遅くなったとき、そしてテーブルにカラムを 1 つ足したときです。この連載は、その差を埋めるために書きます。
動くコードと運用できるコードの差
次の注文 API を見てください。入門記事でよく見る形です。
public function store(Request $request)
{
$validated = $request->validate([
'product_id' => 'required|integer',
'quantity' => 'required|integer|min:1',
'customer_email' => 'required|email',
]);
$stock = DB::table('stocks')->where('product_id', $validated['product_id'])->first();
if ($stock->quantity < $validated['quantity']) {
return response()->json(['message' => '在庫が足りません'], 400);
}
$order = Order::create($validated);
DB::table('stocks')
->where('product_id', $validated['product_id'])
->decrement('quantity', $validated['quantity']);
Mail::raw('ご注文ありがとうございます', function ($message) use ($validated) {
$message->to($validated['customer_email'])->subject('注文を受け付けました');
});
return response()->json($order, 201);
}
読んだかぎり、処理の流れに不自然なところはありません。バリデーションをして在庫を確認し、注文を作って在庫を減らし、確認メールを送っています。リクエストが 1 件ずつ順番に届くかぎり、結果も期待どおりになります。
第3章で作るテーブル構成とは一致していません。そのまま動かすためのものではなく、「どこが壊れるか」を見るためのものとして読んでください。
このコードには、本番で必ず表面化する問題が 4 つあります。
在庫が二重に引き当てられる
在庫の残りが 1 個のときに、2 人が同時に注文したとします。1 人目が在庫を確認した直後、まだ在庫を減らす前に、2 人目の確認処理が走ります。2 人目から見ても在庫は 1 個あるので、チェックを通過します。結果として注文が 2 件作られ、在庫は -1 になります。
これは運が悪いと起きるのではなく、アクセスが増えれば必ず起きます。確認と更新のあいだに他のリクエストが割り込める構造そのものが原因です。第14章で、トランザクションと行ロック、そして条件付きの更新を使って塞ぎます。
メールの送信時間がそのままレスポンス時間になる
Mail::raw() はメールサーバーへの送信が終わるまで次の行に進みません。送信に 3 秒かかれば、注文 API のレスポンスも 3 秒かかります。メールサーバーが落ちていれば、注文そのものが失敗します。
注文を記録することと、確認メールを送ることは、成功の条件が違います。第15章と第16章で、後者をリクエストの外へ出します。
モデルをそのまま返すと、あとで情報が漏れる
response()->json($order, 201) は Order モデルの全カラムを JSON にします。いまは問題なくても、半年後に運用チームが internal_memo というカラムを足した瞬間、その内容が API のレスポンスに現れます。カラムを足した人は、それが外部に公開されるとは思っていません。
レスポンスに何を含めるかは、モデルの都合とは別に決める必要があります。第7章で扱います。
誰でも叩ける
このエンドポイントには認証がありません。customer_email はリクエストで受け取っているので、他人のメールアドレスを書けば他人名義の注文を作れます。第11章で認証を、第12章で認可を入れます。
この連載は、こうした問題を 1 つずつ拾いながら進みます。各章には「本番で効く注意点」という節を置き、その機能を本番で使うときに何が壊れるかを書きます。
題材: EC サイトの注文システム
全 22 章を通して、EC サイトの注文システムを 1 つ育てます。扱うのは商品・在庫・注文・注文明細と、途中から加わる会員です。
この題材を選んだのは、この連載で扱いたい機能が無理なく登場するからです。商品一覧はキャッシュの対象になり、注文確認メールはキューへ回す動機になります。未決済注文の自動キャンセルはタスクスケジューラを呼び、商品画像はファイルストレージを必要とします。「この機能を説明するために題材を用意する」のではなく、題材を育てていくと機能が必要になる、という順番で進みます。
テーブル設計は第3章で決めます。ここでは、章が進むごとに何かが 1 つ足されていく、という進み方だけ掴んでもらえれば十分です。
この連載で作る API
最終的に次のエンドポイントが揃います。
| メソッド | パス | 内容 | 認証 |
|---|---|---|---|
| GET | /api/v1/products | 商品一覧 (ページネーション・絞り込み) | 不要 |
| GET | /api/v1/products/{product} | 商品詳細 | 不要 |
| POST | /api/v1/auth/register | 会員登録 | 不要 |
| POST | /api/v1/auth/login | ログイン (トークン発行) | 不要 |
| POST | /api/v1/auth/logout | ログアウト (トークン失効) | 必要 |
| POST | /api/v1/orders | 注文の作成 | 必要 |
| GET | /api/v1/orders | 自分の注文一覧 | 必要 |
| GET | /api/v1/orders/{order} | 注文詳細 | 必要 |
| GET | /api/v1/orders/{order}/receipt | 領収書 PDF のダウンロード | 必要 |
| POST | /api/v1/products/{product}/image | 商品画像のアップロード | 必要 (専用トークン) |
| POST | /api/v1/webhooks/payment | 決済サービスからの通知受信 | 署名検証 |
エンドポイントの裏側では、次のものも動きます。
- 注文した時点で走る非同期ジョブ (確認メールの送信)
- 入金を確認した時点で走る非同期ジョブ (出荷指示・領収書 PDF の生成)
- 商品一覧のキャッシュと、商品更新時の失効
- 未決済注文を自動でキャンセルする日次バッチ
- ログイン試行と公開エンドポイントへのレート制限
前提とする環境
| 項目 | 値 |
|---|---|
| Laravel | 13 (2026 年 3 月リリース) |
| PHP | 8.3 以上 (Laravel 13 の要件は 8.3 から 8.5) |
| データベース | SQLite (手元で動かす分にはこれで足ります) |
| PHP 拡張 | GD (第19章の画像アップロードのテストで要ります) |
| その他 | Composer、ターミナルの基本操作 |
Laravel は年に 1 回メジャーバージョンが上がります。13 のバグ修正は 2027 年第 3 四半期まで、セキュリティ修正は 2028 年 3 月 17 日までです1。バージョンの選択は、実装の都合ではなく運用計画の一部として決めます。
古い記事を読むときは注意が必要です。少し前のバージョンでいくつかの設定ファイルが整理され、書き方が変わりました。どこがどう変わったかは第2章でまとめて扱います。整理前を前提にした記事は、ファイル構成の時点で現在と噛み合いません。
この連載で扱わないこと
Blade・Livewire・Inertia などのフロントエンド機能は一切扱いません。 全章が JSON を返す API の話です。画面は別で作る前提で読んでください。
ブロードキャスト (Reverb / Echo) も対象外です。サーバー側だけ設定しても、受け取るクライアントを JavaScript で書かなければ動作を確認できません。フロントエンドを扱わないという方針と噛み合わないため、この連載では触れません。
設計論も範囲外です。ドメイン駆動設計の戦術パターンや、クリーンアーキテクチャの層の分け方は扱いません。この連載が示すのは、Laravel の標準的なディレクトリ構成のまま、どこまで整理できるかです。
既存の Laravel ガイドとの読み分け
このサイトには Laravel を扱うガイドが他に 2 つあります。役割が違うので、順番に読むと理解が積み上がります。
| 連載 | 扱うもの | この連載との関係 |
|---|---|---|
| PHPクラス設計ガイド | カプセル化・単一責任・SOLID などの原則 | 第6章でビジネスロジックを切り出すときの判断基準の土台です。原則そのものを知りたくなったらこちらへ |
| Laravel × DDD × クリーンアーキテクチャ実践ガイド | 値オブジェクト・集約・リポジトリ・層の分離 | この連載の次に読むガイドです。「Laravel の標準構成では手狭になってきた」と感じたときに進んでください |
| データベース設計ガイド | 正規化・E-R 図・インデックス設計 | 第3章でテーブルを作るときの理論的な裏付けです |
同じ機能を複数のガイドが扱う場面では、この連載は標準構成でどう書くかを、DDD のガイドはどの層に置くかを担当します。たとえばトランザクションなら、この連載は DB::transaction() の書き方と行ロックを、DDD のガイドは境界をどこに引くかを扱います。
まとめ
- チュートリアルどおりに書いた API は、同時アクセス・外部サービスの遅延・スキーマ変更で壊れる
- この連載は EC の注文システムを 22 章かけて育てながら、その壊れ方を 1 つずつ塞いでいく
- 対象は Laravel 13 と PHP 8.3。フロントエンド機能と設計論は扱わない
- 各章に「本番で効く注意点」の節があり、そこがこの連載の中心
次に読む
次章 リクエストライフサイクルと Laravel 13 の骨格 では、Laravel が 1 つのリクエストをどう処理しているかを追います。ミドルウェアも例外ハンドラもサービスプロバイダも、この経路のどこかに刺さっています。先に地図を持っておくと、後の章で新しい概念が出てきたときに置き場所が分かります。
練習問題
この章の冒頭に出した注文 API のコードを、いま自分が書くとしたらどこから直しますか
解答例
正解は 1 つではありませんが、直す順番には理由が付けられます。
まず塞ぐべきは在庫の二重引き当てです。データが壊れると、あとから復旧するのに手作業が必要になります。レスポンスが遅いのは利用者に不便をかけますが、データは正しいままです。情報漏洩は深刻ですが、いまはまだ漏れる情報がありません (internal_memo はまだ存在しない)。
つまり判断軸は「起きたときに元に戻せるか」です。元に戻せない被害から順に塞ぎます。
この連載では、章の並びの都合で第14章までトランザクションを扱いません。それまでは、あえて壊れたままのコードを持ち歩きます。「なぜこの順番なのか」は各章の冒頭で説明します。