Let’s skip the theory and wire routes like we actually do in production apps.
In Laravel 12, routing lives mainly in:
routes/web.php // browser routes
routes/api.php // stateless API routes
Laravel loads these automatically via the RouteServiceProvider, so you rarely touch bootstrapping.
1. Your First Route (Closure Based)
use Illuminate\Support\Facades\Route;
Route::get('/hello', function () {
return 'Hello Dev!';
});
Why this works
Route::get()registers a route responding to HTTP GET.- First argument = URI.
- Second argument = handler.
- Closure is fine for testing or tiny endpoints.
When NOT to use closures
Closures cannot be cached using php artisan route:cache. In production apps, always move logic into controllers.
2. Routing to Controllers (Real Usage)
Step 1 — Create controller
php artisan make:controller ProductController
Step 2 — Add method
class ProductController extends Controller
{
public function index(): string
{
return 'Product list';
}
}
Step 3 — Register route
use App\Http\Controllers\ProductController;
Route::get('/products', [ProductController::class, 'index']);
Why controller routing matters
- Enables route caching
- Keeps routes clean
- Separates HTTP layer from business logic
3. Route Parameters (Dynamic URLs)
Route::get('/products/{id}', function (int $id) {
return "Product ID: {$id}";
});
Why type-hinting helps (PHP 8.3)
- Laravel casts route parameters
- Helps static analysis
- Prevents accidental string usage
Optional parameter
Route::get('/category/{slug?}', function (?string $slug = null) {
return $slug ?? 'All categories';
});
4. Validation Constraints (Avoid Bad URLs)
Route::get('/user/{id}', function (int $id) {
return $id;
})->whereNumber('id');
Why use constraints
Prevents invalid requests from reaching controller logic.
Alternative:
->where('id', '[0-9]+');
5. Named Routes (Essential for Real Apps)
Route::get('/dashboard', function () {
return 'Dashboard';
})->name('dashboard');
Why naming routes matters
Instead of hardcoding URLs:
route('dashboard');
Benefits:
- URLs can change without breaking views
- Required for redirects and policies
- Cleaner Blade templates
6. Route Groups (Clean Large Projects)
Prefix Group
Route::prefix('admin')->group(function () {
Route::get('/users', fn () => 'Admin Users');
Route::get('/reports', fn () => 'Reports');
});
Now URLs become:
/admin/users
/admin/reports
Middleware Group
Route::middleware(['auth'])->group(function () {
Route::get('/profile', fn () => 'Profile');
});
Why groups matter
- Avoid repetition
- Centralized security rules
- Easier maintenance
7. Resource Routes (CRUD in One Line)
Route::resource('products', ProductController::class);
What Laravel auto-creates
| Method | URI | Action |
|---|---|---|
| GET | /products | index |
| GET | /products/create | create |
| POST | /products | store |
| GET | /products/{product} | show |
| GET | /products/{product}/edit | edit |
| PUT/PATCH | /products/{product} | update |
| DELETE | /products/{product} | destroy |
Why resource routes are powerful
- Standardizes controllers
- Compatible with policies
- Works seamlessly with form helpers
8. Route Model Binding (Huge Time Saver)
Route::get('/products/{product}', function (App\Models\Product $product) {
return $product->name;
});
Why this is better than using ID
Laravel automatically:
- Fetches the model
- Throws 404 if not found
- Injects instance
No manual Product::findOrFail() needed.
9. API Routes Example
Inside routes/api.php:
Route::get('/products', [ProductController::class, 'index']);
Why API routes behave differently
- Automatically prefixed with
/api - Stateless middleware
- No session/cookies
10. Route Caching for Performance
php artisan route:cache
Why this matters
- Converts routes into optimized PHP array
- Huge speed improvement in production
Common mistake
If you use closures, caching fails.
Fix:
Move closures into controllers.
Common Routing Errors & Fixes
1. 404 even though route exists
Cause: Cached routes outdated
Fix:
php artisan route:clear
2. Target class does not exist
Cause: Missing import
Fix:
use App\Http\Controllers\ProductController;
Or regenerate autoload:
composer dump-autoload
3. Method not allowed
Cause: Using POST on GET route
Fix: Check HTTP verb or use:
Route::match(['get','post'], '/form', ...);
4. Route model binding returns 404
Cause: Parameter name mismatch
Example wrong:
/products/{id}
Correct:
/products/{product}
Must match variable name + type-hint.