404 Not Found

404 Not Found


nginx

Laravel ルーティングシステムの詳細解説

ルーティングは Laravel の「受付」です—すべての HTTP リクエストは最初にここで確認され, 対応するコントローラメソッドに振り分けられます。

1. 学習内容


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 ルールを定義し, 名前付け, グループ化, パラメータ制約をサポートしています。一箇所の変更が自動的に全体に反映されます。

PHP
// 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 動詞に対応するルートメソッドを提供しています:

PHP
// 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" ルート

PHP
// 複数の動詞にマッチ
Route::match(['get', 'post'], '/shops/search', [ShopController::class, 'search']);

// すべての動詞にマッチ
Route::any('/fallback', [FallbackController::class, 'handle']);

(1) ▶ サンプル:ShopMetrics 基本ルート定義

PHP
// 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');

出力:

TEXT
// 実行成功

4. ルートパラメータと制約

(1) 必須パラメータ

PHP
Route::get('/shops/{id}', [ShopController::class, 'show']);
Route::get('/tenants/{tenant}/shops/{shop}', [ShopController::class, 'showForTenant']);

(2) オプションパラメータ

PHP
Route::get('/reports/{type?}', [ReportController::class, 'index']);
// /reports → type = null
// /reports/sales → type = 'sales'

(3) 正規表現制約

PHP
// 数値 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 制約付きルート

PHP
// 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-]+');

出力:

TEXT
// 実行成功

5. ルートグループ

ルートグループ化により, 複数のルートで設定 (ミドルウェア, プレフィックス, 名前空間など)を共有でき, コードの重複を防げます。

(1) ミドルウェアグループ化

PHP
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) プレフィックスグループ化

PHP
Route::prefix('admin')->group(function () {
    Route::get('/users', [AdminUserController::class, 'index']);
    Route::get('/settings', [AdminSettingController::class, 'index']);
    // 完全 URL: /admin/users, /admin/settings
});

(3) 名前グループ化

PHP
Route::name('admin.')->group(function () {
    Route::get('/users', [AdminUserController::class, 'index'])->name('users');
    // ルート名: admin.users
});

(4) 組み合わせグループ化

PHP
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 マルチテナントルートグループ

PHP
// 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);
});

出力:

TEXT
// 実行成功

6. ルート名と URL 生成

(1) 名前付きルート

PHP
Route::get('/shops/{id}', [ShopController::class, 'show'])
    ->name('shops.show');

(2) URL の生成

PHP
// 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 で名前付きルートを使用

HTML
<a href="{{ route('shops.show', $shop->id) }}">{{ $shop->name }}</a>

<form action="{{ route('shops.update', $shop->id) }}" method="POST">
    @method('PUT')
    @csrf
    <!-- フォームフィールド -->
</form>

出力:

TEXT
// 実行成功

7. API ルーティング

routes/api.php は API ルーティング専用です。自動的に /api プレフィックスが付与されます。

(1) apiResource ルート

PHP
// 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 バージョン管理

PHP
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 ルーティング設計

PHP
// 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']);
    });
});

出力:

TEXT
// 実行成功

8. ルートマッチングの流れ

100%
flowchart LR
    A[HTTP リクエスト] --> B{ルートにマッチ?}
    B -->|はい| C[パラメータを抽出]
    C --> D[ミドルウェアを実行]
    D --> E[コントローラメソッドを呼び出し]
    E --> F[レスポンスを返す]
    B -->|いいえ| G[フォールバックルート]
    G --> H[404 Not Found]

9. 総合例:ShopMetrics 完全ルーティング設計

PHP
// ============================================
// 総合: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']);
});

❓ よくある質問

Q routes/web.php と routes/api.php の違いは何ですか?
A web.php のルートは自動的に web ミドルウェアグループ (Session, CSRF, Cookie 暗号化)が適用され, ページリクエストに適しています。api.php のルートは自動的に API ミドルウェアグループ (throttle レート制限)が適用され, URL に自動的に /api プレフィックスが付与されるため, API リクエストに適しています。
Q Route::resource はいつ使い, いつ手動でルートを定義すべきですか?
A RESTful な CRUD 操作は resource/apiResource で1行で処理できます。非標準の操作 (検索, エクスポート, 一括操作など)は, 追加のルートを手動で定義してください。
Q ルートに名前を付けるメリットは何ですか?
A 名前付きルートにより, URL とコードが分離されます。URL を変更する際はルート定義を変更するだけで, すべての route() 呼び出しが自動的に更新されます。ハードコードされた URL の場合は, 変更のたびに全体を検索・置換する必要があり, 見落としが発生しやすくなります。
Q shallow ルートとは何ですか?
A デフォルトでは, ネストされたリソースは /shops/{shop}/orders/{order} のような URL を生成します。「shallow」オプションを使用すると, サブリソースは ID が必要な場合のみネストされます:show, edit, update, delete は /orders/{order} を使用し, index と create はネスト構造を維持します。これにより URL の深さが減ります。
Q ルートが多すぎるとパフォーマンスに影響しますか?
A ルートの数はパフォーマンスにほとんど影響しません。Laravel は効率的なマッチングアルゴリズムを使用しているためです。ただし, 1,000 を超えるルートがある場合は, php artisan route:cache でルートテーブルをキャッシュすることをお勧めします。
Q 登録されているすべてのルートを確認するにはどうすればよいですか?
A php artisan route:list を実行すると, メソッド, URI, 名前, ミドルウェアを含むすべてのルートが一覧表示されます。--path=shops を追加すると, 特定のプレフィックスでルートをフィルタリングできます。

📖 まとめ


📝 練習問題

  1. 基本問題 (⭐):ShopMetrics の以下のルートを定義してください:ホームページ (GET /), About ページ (GET /about), Contact ページ (GET+POST /contact)。名前付きルートを使用し, ブラウザでアクセスできることを確認してください。

  2. 応用問題 (⭐⭐):ルートグループを使用して, ShopMetrics の API v1 ルート設計を作成してください。shops, products, orders の3つの apiResource と, 認証ミドルウェア, /api/v1 プレフィックスを含めてください。

  3. チャレンジ (⭐⭐⭐):マルチテナントルートグループ /{tenant}/* を実装してください。TenantResolve ミドルウェアを作成して URL からテナントを解析し Request に注入し, すべてのサブルートで $request->tenant() から現在のテナントを取得できるようにしてください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%