A Detailed Explanation of the Laravel Routing System
Routes are Laravel's "front desk"—every HTTP request first checks in here before being routed to the corresponding controller method.
1. What You'll Learn
- Basic Routes: Route::get/post/put/patch/delete
- Route Parameters and Regular Expression Constraints
- Route grouping: middleware/prefix/name/domain
- Route Naming and URL Generation
- API Routing and Route::apiResource()
2. A Product Manager's True Story
(1) Pain Point: Chaotic URL Structures Lead to an SEO Disaster
Alice designed over 30 pages for ShopMetrics, but the URL naming convention was completely haphazard: /shop_view.php?id=5, /admin-users-list, and /api/getData were all mixed together. Google's crawler was extremely inefficient, and when users shared links, the address bar displayed a string of question mark parameters. When Bob restructured the backend and changed a single URL, all 15 hard-coded instances on the front end returned 404 errors.
(2) Solutions for Laravel Routing
Laravel routing uses declarative syntax to define URL rules, supporting naming, grouping, and parameter constraints; changes made in one place automatically take effect globally.
// routes/web.php — Clean, named, RESTful routes
Route::get('/shops/{slug}', [ShopController::class, 'show'])
->name('shops.show')
->where('slug', '[a-z0-9-]+');
// Generate URL by name — never hardcode
$url = route('shops.show', ['slug' => 'alice-store']);
// => /shops/alice-store
(3) Revenue
Alice standardized ShopMetrics' URL rules using named routes, resulting in a 30% increase in SEO rankings. When Bob refactored the URLs, he only needed to update the route definitions; the front end route() automatically generated the new URLs, resulting in zero 404 errors.
3. Basic Routing
(1) HTTP Verb Routing
Laravel provides a corresponding route method for each HTTP verb:
// 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 Verb | Purpose | Idempotency | Typical Operations |
|---|---|---|---|
| GET | Retrieve Resource | ✅ | List/Details |
| POST | Create Resource | ❌ | Add |
| PUT | Full Update | ✅ | Replace |
| PATCH | Partial Update | ✅ | Status Change |
| DELETE | Delete Resource | ✅ | Delete |
(2) "match" and "any" Routes
// Match multiple verbs
Route::match(['get', 'post'], '/shops/search', [ShopController::class, 'search']);
// Any verb
Route::any('/fallback', [FallbackController::class, 'handle']);
(1) ▶ Example: ShopMetrics Basic Route Definitions
// 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');
Output:
// Execution Successful
4. Routing Parameters and Constraints
(1) Required Parameters
Route::get('/shops/{id}', [ShopController::class, 'show']);
Route::get('/tenants/{tenant}/shops/{shop}', [ShopController::class, 'showForTenant']);
(2) Optional Parameters
Route::get('/reports/{type?}', [ReportController::class, 'index']);
// /reports → type = null
// /reports/sales → type = 'sales'
(3) Regular Expressions
// Numeric ID only
Route::get('/shops/{id}', [ShopController::class, 'show'])
->where('id', '[0-9]+');
// Slug format: lowercase, numbers, hyphens
Route::get('/shops/{slug}', [ShopController::class, 'showBySlug'])
->where('slug', '[a-z0-9-]+');
// Multiple constraints
Route::get('/tenants/{tenant}/orders/{id}', [OrderController::class, 'show'])
->where(['tenant' => '[a-z0-9-]+', 'id' => '[0-9]+']);
| Constraint Method | Usage | Description |
|---|---|---|
where() |
Single-parameter regular expression | Most flexible |
whereNumber() |
Numbers only | Equivalent to where('id', '[0-9]+') |
whereAlpha() |
Letters only | Equivalent to where('name', '[a-zA-Z]+') |
whereAlphaNumeric() |
Letters + Numbers | Equivalent where('name', '[a-zA-Z0-9]+') |
whereUuid() |
UUID Format | Automatic UUID v4 Validation |
(1) ▶ Example: ShopMetrics Routes with Constraints
// 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-]+');
Output:
// Execution Successful
5. Routing Groups
Route grouping allows multiple routes to share configurations (middleware, prefixes, namespaces, etc.), thereby avoiding code duplication.
(1) Middleware Grouping
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) Prefix Grouping
Route::prefix('admin')->group(function () {
Route::get('/users', [AdminUserController::class, 'index']);
Route::get('/settings', [AdminSettingController::class, 'index']);
// Full URL: /admin/users, /admin/settings
});
(3) Name Grouping
Route::name('admin.')->group(function () {
Route::get('/users', [AdminUserController::class, 'index'])->name('users');
// Route name: admin.users
});
(4) Combination and Grouping
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, name: admin.users
});
(1) ▶ Example: ShopMetrics Multitenant Routing Groups
// routes/web.php — Tenant-aware routes
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);
});
Output:
// Execution Successful
6. Route Naming and URL Generation
(1) Named Routes
Route::get('/shops/{id}', [ShopController::class, 'show'])
->name('shops.show');
(2) Generate a URL
// In Blade templates or controllers
$url = route('shops.show', ['id' => 5]);
// => http://shopmetrics.test/shops/5
// With query parameters
$url = route('shops.index', ['sort' => 'name', 'page' => 2]);
// => http://shopmetrics.test/shops?sort=name&page=2
| Function | Purpose | Example |
|---|---|---|
route() |
Generate a named route URL | route('shops.show', 5) |
url() |
Generate Absolute URL | url('/shops') |
action() |
Generated based on the controller method | action([ShopController::class, 'show'], 5) |
(1) ▶ Example: Using Named Routes in Blade
<a href="{{ route('shops.show', $shop->id) }}">{{ $shop->name }}</a>
<form action="{{ route('shops.update', $shop->id) }}" method="POST">
@method('PUT')
@csrf
<!-- form fields -->
</form>
Output:
// Execution Successful
7. API Routing
routes/api.php Used exclusively for API routing; automatically adds the /api prefix.
(1) apiResource Route
// routes/api.php
use App\Http\Controllers\Api\ShopController;
Route::apiResource('shops', ShopController::class);
// Generates:
// GET /api/shops → index
// POST /api/shops → store
// GET /api/shops/{shop} → show
// PUT /api/shops/{shop} → update
// DELETE /api/shops/{shop} → destroy
| Method | apiResource | resource |
|---|---|---|
| index | ✅ | ✅ |
| create | ❌ | ✅ |
| store | ✅ | ✅ |
| show | ✅ | ✅ |
| edit | ❌ | ✅ |
| update | ✅ | ✅ |
| destroy | ✅ | ✅ |
(2) API Version Control
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) ▶ Example: ShopMetrics API Routing Blueprint
// 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']);
});
});
Output:
// Execution Successful
8. Route Matching Process
flowchart LR
A[HTTP Request] --> B{Match Route?}
B -->|Yes| C[Extract Parameters]
C --> D[Run Middleware]
D --> E[Call Controller Method]
E --> F[Return Response]
B -->|No| G[Fallback Route]
G --> H[404 Not Found]
9. Comprehensive Example: ShopMetrics Complete Routing Blueprint
// ============================================
// Comprehensive: ShopMetrics complete routes
// Covers: web routes, api routes, groups, constraints
// ============================================
// 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']);
});
❓ FAQ
web.php automatically apply the web middleware group (Session, CSRF, cookie encryption), making it suitable for page requests; routes in api.php automatically apply the API middleware group (throttle rate limiting), and URLs are automatically prefixed with /api, making it suitable for API requests.Route::resource, and when should I define routes manually?resource/apiResource; for non-standard operations (such as search, export, and bulk operations), define additional routes manually.route() calls are automatically updated. With hard-coded URLs, you have to perform a global search-and-replace whenever you make a change, which makes it easy to miss some instances.php artisan route:cache.php artisan route:list to list all routes, including the method, URI, name, and middleware. Add --path=shops to filter routes by a specific prefix.📖 Summary
- Laravel provides routing methods for each HTTP verb: GET, POST, PUT, PATCH, and DELETE
- Route parameters can be required or optional; you can use
where()to add regular expression constraints - Route grouping allows multiple routes to share middleware, prefixes, and namespaces
- Use named routes in conjunction with the
route()function to decouple URLs from code - apiResource: Automatically generates RESTful API routes (excluding create/edit)
- Route caching (route:cache) can significantly improve the performance of matching a large number of routes
📝 Exercises
-
Basic Exercise (⭐): Define the following routes for ShopMetrics: Home page (GET /), About page (GET /about), and Contact page (GET+POST /contact). Use named routes and verify that they are accessible in a browser.
-
Advanced Exercise (⭐⭐): Use route groups to design an API v1 route blueprint for ShopMetrics, including three apiResources—shops, products, and orders—along with authentication middleware and the /api/v1 prefix.
-
Challenge (⭐⭐⭐): Implement multi-tenant route grouping
/{tenant}/*. Write a TenantResolve middleware to parse the tenant from the URL and inject it into the Request, ensuring that all sub-routes can retrieve the current tenant via$request->tenant().



