Reader Stacks

Laravel Routing: A Practical Guide

Beyond a basic Route::get(), the features that actually shape a real app's routes are parameters, route model binding, named routes, and grouping shared middleware or prefixes together.

Laravel's routing starts simple — matching a URL to a closure or controller method — but a handful of features beyond the basics are what actually structure how routes are organized in a real application.

1. The basics

Route::get('/products', [ProductController::class, 'index']);
Route::post('/products', [ProductController::class, 'store']);
Route::get('/products/{id}', [ProductController::class, 'show']);

2. Route parameters

Route::get('/products/{id}', function (string $id) {
    return "Product #{$id}";
});

Route::get('/products/{category?}', function (?string $category = null) {
    // {category?} is optional — the closure needs a default value to match
});

3. Route model binding — skip the manual lookup entirely

Route::get('/products/{product}', function (Product $product) {
    return $product->name;
});

When the parameter name matches a type-hinted Eloquent model in the route's method signature, Laravel automatically looks up that model by its route key (the primary key, by default) and injects the actual model instance — or automatically returns a 404 if no matching record exists. This replaces a manual Product::findOrFail($id) call at the top of the method entirely.

// Using a different column (e.g. slug) instead of the primary key
Route::get('/products/{product:slug}', [ProductController::class, 'show']);

4. Named routes

Route::get('/products/{id}', [ProductController::class, 'show'])->name('products.show');
<a href="{{ route('products.show', $product->id) }}">{{ $product->name }}</a>

Referencing routes by name rather than hardcoding the literal URL string everywhere means the actual URL pattern can change later (from /products/{id} to /items/{id}, for instance) without needing to find and update every hardcoded link throughout the app — only the route definition itself changes.

5. Route groups — shared prefix, middleware, or namespace

Route::middleware('auth')->prefix('admin')->name('admin.')->group(function () {
    Route::get('/dashboard', [AdminController::class, 'dashboard'])->name('dashboard');
    Route::get('/users', [AdminController::class, 'users'])->name('users');
});

Every route inside this group automatically requires the auth middleware, is prefixed with /admin, and has its name prefixed with admin. — this avoids repeating the same middleware and prefix declaration on every individual route that shares them, and keeps related routes visually grouped together in the routes file.

6. Resource routes — the standard CRUD set in one line

Route::resource('products', ProductController::class);

This single line generates all seven conventional RESTful routes (index, create, store, show, edit, update, destroy) mapped to matching controller methods — the standard shortcut for a controller that follows Laravel's conventional CRUD method naming, instead of declaring each of the seven routes individually.

7. Route caching for production

php artisan route:cache

For an app with a large number of routes, caching them compiles the whole route table into a single optimized file, meaningfully speeding up route resolution on each request — this needs to be cleared and regenerated (route:clear, then route:cache again) any time routes are changed and redeployed, or the cached, now-stale route table will keep being used instead.

Topics: Developer Productivity