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.