Laravel ルーティングシステムの詳細解説
ルーティングは Laravel の「受付」です—すべての HTTP リクエストは最初にここで確認され, 対応するコントローラメソッドに振り分けられます。
1. 学習内容
- 基本ルート:Route::get/post/put/patch/delete
- ルートパラメータと正規表現制約
- ルートグループ化:middleware/prefix/name/domain
- ルート名と URL 生成
- API ルーティングと Route::apiResource()
2. プロダクトマネージャーの実話
(1) 課題:混沌とした URL 構造が SEO 災害を引き起こす
Alice は ShopMetrics のために 30 以上のページを設計しましたが, URL の命名規則は完全にバラバラでした:/shop_view.php?id=5, /admin-users-list, /api/getData が混在していました。Google のクローラーは非常に非効率で, ユーザーがリンクを共有すると, アドレスバーにはクエスチョンマークのパラメータが表示されていました。Bob がバックエンドを再構築してひとつの URL を変更したところ, フロントエンドの 15 箇所のハードコードすべてが 404 エラーを返しました。
(2) Laravel ルーティングの解決策
Laravel ルーティングは宣言的構文で URL ルールを定義し, 名前付け, グループ化, パラメータ制約をサポートしています。一箇所の変更が自動的に全体に反映されます。
// routes/web.php — 整理された名前付き RESTful ルート
Route::get('/shops/{slug}', [ShopController::class, 'show'])
->name('shops.show')
->where('slug', '[a-z0-9-]+');
// 名前で URL を生成 — ハードコードしない
$url = route('shops.show', ['slug' => 'alice-store']);
// => /shops/alice-store
(3) 成果
Alice は名前付きルートを使って ShopMetrics の URL ルールを標準化し, SEO ランキングが 30% 向上しました。Bob が URL をリファクタリングする際は, ルート定義を更新するだけで済み, フロントエンドの route() が自動的に新しい URL を生成するため, 404 エラーはゼロになりました。
3. 基本ルーティング
(1) HTTP 動詞ルーティング
Laravel は各 HTTP 動詞に対応するルートメソッドを提供しています:
// routes/web.php
Route::get('/shops', [ShopController::class, 'index']);
Route::post('/shops', [ShopController::class, 'store']);
Route::put('/shops/{id}', [ShopController::class, 'update']);
Route::patch('/shops/{id}', [ShopController::class, 'updateStatus']);
Route::delete('/shops/{id}', [ShopController::class, 'destroy']);
| HTTP 動詞 | 目的 | べき等性 | 代表的な操作 |
|---|---|---|---|
| GET | リソースの取得 | ✅ | 一覧/詳細 |
| POST | リソースの作成 | ❌ | 追加 |
| PUT | 全体更新 | ✅ | 置換 |
| PATCH | 部分更新 | ✅ | ステータス変更 |
| DELETE | リソースの削除 | ✅ | 削除 |
(2) "match" と "any" ルート
// 複数の動詞にマッチ
Route::match(['get', 'post'], '/shops/search', [ShopController::class, 'search']);
// すべての動詞にマッチ
Route::any('/fallback', [FallbackController::class, 'handle']);
(1) ▶ サンプル:ShopMetrics 基本ルート定義
// routes/web.php
Route::get('/', [HomeController::class, 'index'])->name('home');
Route::get('/about', [AboutController::class, 'index'])->name('about');
Route::get('/pricing', [PricingController::class, 'index'])->name('pricing');
Route::get('/contact', [ContactController::class, 'create'])->name('contact.create');
Route::post('/contact', [ContactController::class, 'store'])->name('contact.store');
出力:
// 実行成功
4. ルートパラメータと制約
(1) 必須パラメータ
Route::get('/shops/{id}', [ShopController::class, 'show']);
Route::get('/tenants/{tenant}/shops/{shop}', [ShopController::class, 'showForTenant']);
(2) オプションパラメータ
Route::get('/reports/{type?}', [ReportController::class, 'index']);
// /reports → type = null
// /reports/sales → type = 'sales'
(3) 正規表現制約
// 数値 ID のみ
Route::get('/shops/{id}', [ShopController::class, 'show'])
->where('id', '[0-9]+');
// スラッグ形式:小文字, 数字, ハイフン
Route::get('/shops/{slug}', [ShopController::class, 'showBySlug'])
->where('slug', '[a-z0-9-]+');
// 複数の制約
Route::get('/tenants/{tenant}/orders/{id}', [OrderController::class, 'show'])
->where(['tenant' => '[a-z0-9-]+', 'id' => '[0-9]+']);
| 制約メソッド | 用途 | 説明 |
|---|---|---|
where() |
単一パラメータの正規表現 | 最も柔軟 |
whereNumber() |
数値のみ | where('id', '[0-9]+') と同等 |
whereAlpha() |
英字のみ | where('name', '[a-zA-Z]+') と同等 |
whereAlphaNumeric() |
英字+数字 | where('name', '[a-zA-Z0-9]+') と同等 |
whereUuid() |
UUID 形式 | UUID v4 の自動検証 |
(1) ▶ サンプル:ShopMetrics 制約付きルート
// routes/web.php
Route::get('/shops/{id}', [ShopController::class, 'show'])
->whereNumber('id');
Route::get('/categories/{slug}', [CategoryController::class, 'show'])
->where('slug', '[a-z0-9-]+');
Route::get('/tenants/{tenant}/dashboard', [DashboardController::class, 'index'])
->where('tenant', '[a-z0-9-]+');
出力:
// 実行成功
5. ルートグループ
ルートグループ化により, 複数のルートで設定 (ミドルウェア, プレフィックス, 名前空間など)を共有でき, コードの重複を防げます。
(1) ミドルウェアグループ化
Route::middleware(['auth', 'tenant.resolve'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
Route::get('/shops', [ShopController::class, 'index']);
Route::get('/orders', [OrderController::class, 'index']);
});
(2) プレフィックスグループ化
Route::prefix('admin')->group(function () {
Route::get('/users', [AdminUserController::class, 'index']);
Route::get('/settings', [AdminSettingController::class, 'index']);
// 完全 URL: /admin/users, /admin/settings
});
(3) 名前グループ化
Route::name('admin.')->group(function () {
Route::get('/users', [AdminUserController::class, 'index'])->name('users');
// ルート名: admin.users
});
(4) 組み合わせグループ化
Route::prefix('admin')
->middleware(['auth', 'admin'])
->name('admin.')
->group(function () {
Route::get('/users', [AdminUserController::class, 'index'])->name('users');
Route::get('/plans', [AdminPlanController::class, 'index'])->name('plans');
// URL: /admin/users, 名前: admin.users
});
(1) ▶ サンプル:ShopMetrics マルチテナントルートグループ
// routes/web.php — テナント対応ルート
Route::middleware(['auth', 'tenant.resolve'])->prefix('/{tenant}')->group(function () {
Route::get('/dashboard', [TenantDashboardController::class, 'index'])
->name('tenant.dashboard');
Route::resource('/shops', ShopController::class);
Route::resource('/orders', OrderController::class);
Route::resource('/products', ProductController::class);
});
出力:
// 実行成功
6. ルート名と URL 生成
(1) 名前付きルート
Route::get('/shops/{id}', [ShopController::class, 'show'])
->name('shops.show');
(2) URL の生成
// Blade テンプレートやコントローラ内で
$url = route('shops.show', ['id' => 5]);
// => http://shopmetrics.test/shops/5
// クエリパラメータ付き
$url = route('shops.index', ['sort' => 'name', 'page' => 2]);
// => http://shopmetrics.test/shops?sort=name&page=2
| 関数 | 目的 | 例 |
|---|---|---|
route() |
名前付きルートの URL を生成 | route('shops.show', 5) |
url() |
絶対 URL を生成 | url('/shops') |
action() |
コントローラメソッドから生成 | action([ShopController::class, 'show'], 5) |
(1) ▶ サンプル:Blade で名前付きルートを使用
<a href="{{ route('shops.show', $shop->id) }}">{{ $shop->name }}</a>
<form action="{{ route('shops.update', $shop->id) }}" method="POST">
@method('PUT')
@csrf
<!-- フォームフィールド -->
</form>
出力:
// 実行成功
7. API ルーティング
routes/api.php は API ルーティング専用です。自動的に /api プレフィックスが付与されます。
(1) apiResource ルート
// routes/api.php
use App\Http\Controllers\Api\ShopController;
Route::apiResource('shops', ShopController::class);
// 生成されるルート:
// GET /api/shops → index
// POST /api/shops → store
// GET /api/shops/{shop} → show
// PUT /api/shops/{shop} → update
// DELETE /api/shops/{shop} → destroy
| メソッド | apiResource | resource |
|---|---|---|
| index | ✅ | ✅ |
| create | ❌ | ✅ |
| store | ✅ | ✅ |
| show | ✅ | ✅ |
| edit | ❌ | ✅ |
| update | ✅ | ✅ |
| destroy | ✅ | ✅ |
(2) API バージョン管理
Route::prefix('v1')->group(function () {
Route::apiResource('shops', Api\V1\ShopController::class);
Route::apiResource('orders', Api\V1\OrderController::class);
});
Route::prefix('v2')->group(function () {
Route::apiResource('shops', Api\V2\ShopController::class);
});
(1) ▶ サンプル:ShopMetrics API ルーティング設計
// routes/api.php
Route::middleware('auth:sanctum')->group(function () {
Route::prefix('v1')->name('api.v1.')->group(function () {
Route::apiResource('tenants.shops', Api\V1\TenantShopController::class);
Route::apiResource('shops.orders', Api\V1\ShopOrderController::class);
Route::apiResource('products', Api\V1\ProductController::class);
Route::get('analytics/overview', [Api\V1\AnalyticsController::class, 'overview']);
Route::post('reports/generate', [Api\V1\ReportController::class, 'generate']);
});
});
出力:
// 実行成功
8. ルートマッチングの流れ
flowchart LR
A[HTTP リクエスト] --> B{ルートにマッチ?}
B -->|はい| C[パラメータを抽出]
C --> D[ミドルウェアを実行]
D --> E[コントローラメソッドを呼び出し]
E --> F[レスポンスを返す]
B -->|いいえ| G[フォールバックルート]
G --> H[404 Not Found]
9. 総合例:ShopMetrics 完全ルーティング設計
// ============================================
// 総合:ShopMetrics の完全ルート
// 対象:web ルート, api ルート, グループ, 制約
// ============================================
// routes/web.php
Route::get('/', [HomeController::class, 'index'])->name('home');
Route::get('/pricing', [PricingController::class, 'index'])->name('pricing');
Route::middleware('auth')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
Route::resource('shops', ShopController::class)->whereNumber('shop');
Route::resource('shops.orders', OrderController::class)->shallow();
Route::post('/shops/{shop}/logo', [ShopLogoController::class, 'update'])
->name('shops.logo.update');
});
// routes/api.php
Route::prefix('v1')->middleware('auth:sanctum')->group(function () {
Route::apiResource('shops', Api\ShopController::class);
Route::apiResource('shops.products', Api\ProductController::class)->shallow();
Route::apiResource('shops.orders', Api\OrderController::class)->shallow();
Route::get('analytics/summary', [Api\AnalyticsController::class, 'summary']);
Route::post('reports/generate', [Api\ReportController::class, 'generate']);
});
❓ よくある質問
web.php のルートは自動的に web ミドルウェアグループ (Session, CSRF, Cookie 暗号化)が適用され, ページリクエストに適しています。api.php のルートは自動的に API ミドルウェアグループ (throttle レート制限)が適用され, URL に自動的に /api プレフィックスが付与されるため, API リクエストに適しています。Route::resource はいつ使い, いつ手動でルートを定義すべきですか?resource/apiResource で1行で処理できます。非標準の操作 (検索, エクスポート, 一括操作など)は, 追加のルートを手動で定義してください。route() 呼び出しが自動的に更新されます。ハードコードされた URL の場合は, 変更のたびに全体を検索・置換する必要があり, 見落としが発生しやすくなります。php artisan route:cache でルートテーブルをキャッシュすることをお勧めします。php artisan route:list を実行すると, メソッド, URI, 名前, ミドルウェアを含むすべてのルートが一覧表示されます。--path=shops を追加すると, 特定のプレフィックスでルートをフィルタリングできます。📖 まとめ
- Laravel は各 HTTP 動詞にルーティングメソッドを提供しています:GET, POST, PUT, PATCH, DELETE
- ルートパラメータは必須またはオプションにでき,
where()で正規表現制約を追加できます - ルートグループ化により, 複数のルートでミドルウェア, プレフィックス, 名前空間を共有できます
- 名前付きルートと
route()関数を組み合わせて, URL とコードを分離しましょう - apiResource:RESTful API ルートを自動生成します (create/edit は除く)
- ルートキャッシュ (route:cache)により, 大量ルートのマッチング性能を大幅に向上できます
📝 練習問題
-
基本問題 (⭐):ShopMetrics の以下のルートを定義してください:ホームページ (GET /), About ページ (GET /about), Contact ページ (GET+POST /contact)。名前付きルートを使用し, ブラウザでアクセスできることを確認してください。
-
応用問題 (⭐⭐):ルートグループを使用して, ShopMetrics の API v1 ルート設計を作成してください。shops, products, orders の3つの apiResource と, 認証ミドルウェア, /api/v1 プレフィックスを含めてください。
-
チャレンジ (⭐⭐⭐):マルチテナントルートグループ
/{tenant}/*を実装してください。TenantResolve ミドルウェアを作成して URL からテナントを解析し Request に注入し, すべてのサブルートで$request->tenant()から現在のテナントを取得できるようにしてください。



