Introduction to Laravel Routing
Routing is the process of mapping URLs to specific actions in your application. Think of routes as the switchboard operators of your application - they direct incoming calls (requests) to the appropriate departments (controllers or closures).
In real-world terms, routing is similar to how a post office sorts mail by zip code and address - each piece of mail (HTTP request) gets directed to its proper destination (controller action) based on its address (URL).
Route Files
Laravel organizes routes into separate files based on their purpose:
- routes/web.php - Routes for web interface (browser requests, typically returning HTML)
- routes/api.php - Routes for API endpoints (typically returning JSON)
- routes/console.php - Routes for custom Artisan commands
- routes/channels.php - Routes for broadcasting channels (WebSockets)
This separation is like organizing your documents into different folders by type - it helps maintain clarity and makes it easier to find what you're looking for.
// routes/web.php - Web routes
Route::get('/', function () {
return view('welcome');
});
// routes/api.php - API routes
Route::get('/users', [UserController::class, 'index']);
An important distinction: routes in api.php are automatically prefixed with /api and assigned to the api middleware group.
Basic Route Definitions
Laravel provides expressive methods for defining routes for different HTTP verbs:
// Basic GET route
Route::get('/hello', function () {
return 'Hello World';
});
// Route with parameters
Route::get('/users/{id}', function ($id) {
return 'User ' . $id;
});
// Named route
Route::get('/profile', [ProfileController::class, 'show'])->name('profile');
// Route with multiple HTTP verbs
Route::match(['get', 'post'], '/contact', [ContactController::class, 'handleContact']);
// Route for all HTTP verbs
Route::any('/webhook', [WebhookController::class, 'handle']);
Each HTTP verb corresponds to a specific type of action:
- GET - Retrieve information (read)
- POST - Create new resources
- PUT/PATCH - Update existing resources
- DELETE - Remove resources
This aligns with the CRUD (Create, Read, Update, Delete) operations in most applications.
As an analogy, these HTTP verbs are like different types of requests you might make at a library:
- GET - "Can I see this book?" (viewing only)
- POST - "Here's a new book to add to your collection."
- PUT/PATCH - "Please update this book's information."
- DELETE - "Please remove this book from your collection."
Route Parameters
Routes often need to capture parts of the URL as parameters. Laravel makes this straightforward:
Required Parameters
Route::get('/users/{id}', function ($id) {
return 'User with ID: ' . $id;
});
Optional Parameters
Route::get('/users/{id?}', function ($id = null) {
return $id ? 'User ' . $id : 'All users';
});
Parameters with Constraints
Route::get('/users/{id}', function ($id) {
return 'User ' . $id;
})->where('id', '[0-9]+');
// Multiple constraints
Route::get('/posts/{slug}/{id}', function ($slug, $id) {
return "Post $slug with ID $id";
})->where(['slug' => '[A-Za-z0-9\-]+', 'id' => '[0-9]+']);
// Global constraints in RouteServiceProvider
// public function boot()
// {
// Route::pattern('id', '[0-9]+');
// // ...
// }
Route parameters work like variables in a function - they capture dynamic parts of the URL and make them available to your code.
Think of route parameters as form fields in an address - some fields are required (like the street name), others are optional (like an apartment number), and some have specific formats (like a zip code that must be numeric).
Named Routes
Naming routes allows you to reference them without hardcoding URLs, making your application more maintainable:
// Defining a named route
Route::get('/user/profile', [ProfileController::class, 'show'])->name('profile');
// Generating URLs to named routes
$url = route('profile'); // /user/profile
// Generating URLs with parameters
Route::get('/user/{id}/profile', [ProfileController::class, 'show'])->name('profile.show');
$url = route('profile.show', ['id' => 1]); // /user/1/profile
// Redirecting to named routes
return redirect()->route('profile');
Named routes are like contacts in your phone - instead of remembering everyone's phone number (URL structure), you can simply select their name (route name) to call them.
This approach has several advantages:
- Decoupling - Your code doesn't depend on specific URL structures
- Maintenance - URLs can change without affecting the code that links to them
- Readability - Route names often provide more context than raw URLs
- DRY Principle - Define routes once, reference them many times
Route Groups
Route groups allow you to share attributes across multiple routes, such as middleware, namespaces, prefixes, and more:
// Route group with prefix
Route::prefix('admin')->group(function () {
Route::get('/users', [AdminController::class, 'users']);
Route::get('/settings', [AdminController::class, 'settings']);
// Both routes are prefixed with /admin
});
// Route group with middleware
Route::middleware('auth')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
Route::get('/profile', [ProfileController::class, 'show']);
// Both routes use the auth middleware
});
// Combined attributes
Route::prefix('admin')->middleware(['auth', 'admin'])->group(function () {
// Routes with prefix /admin and middlewares auth & admin
Route::get('/users', [AdminController::class, 'users']);
// More routes...
});
Route groups act like organizational departments - they group related functionalities and apply common policies (middleware) to them.
Nested Route Groups
Route::prefix('api')->group(function () {
Route::prefix('v1')->group(function () {
Route::get('/users', [ApiController::class, 'usersV1']);
});
Route::prefix('v2')->group(function () {
Route::get('/users', [ApiController::class, 'usersV2']);
});
});
// Creates /api/v1/users and /api/v2/users
This hierarchical organization is like nested folders in a file system - it helps organize routes logically based on their relationships.
Route Model Binding
Laravel's route model binding automatically resolves Eloquent models from route parameters, saving you from manually querying the database:
Implicit Binding
// Route definition
Route::get('/users/{user}', [UserController::class, 'show']);
// Controller method
public function show(User $user)
{
return view('users.show', ['user' => $user]);
}
Laravel automatically resolves {user} to a User model instance by matching the route parameter value against the model's primary key. If no matching model exists, a 404 response is generated.
Explicit Binding
// In RouteServiceProvider::boot
public function boot()
{
Route::bind('user', function ($value) {
return User::where('username', $value)->firstOrFail();
});
}
// Route definition
Route::get('/users/{user}', [UserController::class, 'show']);
Route model binding is like having an assistant who automatically retrieves the file you need whenever you mention its name - it eliminates the repetitive task of looking up records by ID.
Custom Keys for Model Binding
// Resolving by username instead of ID
public function resolveRouteBinding($value, $field = null)
{
return $this->where('username', $value)->firstOrFail();
}
This customization allows you to use more user-friendly identifiers in your URLs, such as usernames or slugs, instead of database IDs.
Introduction to Controllers
Controllers are the orchestra conductors of your application - they coordinate the interaction between the HTTP request, models, and views. They provide a central place to group related request handling logic.
Creating Controllers
// Create a basic controller
php artisan make:controller UserController
// Create a controller with resource methods
php artisan make:controller ProductController --resource
// Create a controller with a model binding
php artisan make:controller OrderController --model=Order
Basic Controller Structure
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function index()
{
$users = User::all();
return view('users.index', ['users' => $users]);
}
public function show($id)
{
$user = User::findOrFail($id);
return view('users.show', ['user' => $user]);
}
}
Controllers typically contain methods that correspond to different actions your application can perform, such as listing records, showing details, creating new records, etc.
Controller Route Binding
There are multiple ways to connect controllers to routes:
Method 1: Individual Route Definitions
Route::get('/users', [UserController::class, 'index']);
Route::get('/users/{id}', [UserController::class, 'show']);
Route::post('/users', [UserController::class, 'store']);
Method 2: Resource Controllers
// Register all resource routes
Route::resource('photos', PhotoController::class);
// Register only specific resource routes
Route::resource('photos', PhotoController::class)->only([
'index', 'show'
]);
// Register all resource routes except specific ones
Route::resource('photos', PhotoController::class)->except([
'create', 'store', 'update', 'destroy'
]);
Resource controllers automatically map conventional routes to controller methods:
| HTTP Verb | URI | Action | Route Name |
|---|---|---|---|
| GET | /photos | index | photos.index |
| GET | /photos/create | create | photos.create |
| POST | /photos | store | photos.store |
| GET | /photos/{photo} | show | photos.show |
| GET | /photos/{photo}/edit | edit | photos.edit |
| PUT/PATCH | /photos/{photo} | update | photos.update |
| DELETE | /photos/{photo} | destroy | photos.destroy |
Resource controllers implement the CRUD pattern, providing a standardized approach to resource management. It's like having standardized forms for common business procedures - everyone knows what to expect and how to use them.
Single Action Controllers
For controllers that handle only a single action, Laravel offers single action controllers with the __invoke method:
// SingleActionController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ShowDashboard extends Controller
{
public function __invoke(Request $request)
{
return view('dashboard');
}
}
// Route definition
Route::get('/dashboard', ShowDashboard::class);
Single action controllers are like specialized tools that do one job extremely well - they're perfect for focused, single-purpose actions.
Creating Single Action Controllers
php artisan make:controller ShowDashboard --invokable
Controller Middleware
Middleware can be applied to controllers to filter HTTP requests before they reach your controller actions:
Method 1: In Route Definitions
Route::get('/profile', [ProfileController::class, 'show'])->middleware('auth');
Method 2: In Controller Constructor
class UserController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('log')->only('index');
$this->middleware('subscribed')->except('store');
}
}
Controller middleware is like security checks or preparatory procedures that happen before the main event. For example, the auth middleware ensures only authenticated users can access certain controller actions, similar to how a security guard checks badges before allowing entry to restricted areas.
Dependency Injection in Controllers
Laravel's service container automatically resolves any dependencies declared in your controller method signatures:
public function store(Request $request, PostRepository $posts)
{
$posts->create($request->all());
return redirect()->route('posts.index');
}
In this example, Laravel automatically injects the current Request instance and resolves the PostRepository from the service container.
Dependency injection is like having assistants who automatically bring you exactly what you need for a task without you having to fetch everything yourself.
Method Injection vs. Constructor Injection
// Constructor injection
class UserController extends Controller
{
protected $users;
public function __construct(UserRepository $users)
{
$this->users = $users;
}
public function show($id)
{
$user = $this->users->find($id);
return view('users.show', ['user' => $user]);
}
}
// Method injection
class ProductController extends Controller
{
public function show($id, ProductRepository $products)
{
$product = $products->find($id);
return view('products.show', ['product' => $product]);
}
}
Choose constructor injection for dependencies used across multiple controller methods, and method injection for dependencies needed only by specific methods.
Request Handling in Controllers
Controllers process incoming HTTP requests and generate appropriate responses:
Accessing Request Data
public function store(Request $request)
{
// Access all input data
$all = $request->all();
// Access specific input field
$name = $request->input('name');
// Access with default value if not present
$quantity = $request->input('quantity', 1);
// Check if input field exists
if ($request->has('product_id')) {
// ...
}
// Retrieve only specific fields
$credentials = $request->only(['email', 'password']);
// Retrieve all except specific fields
$data = $request->except(['credit_card']);
}
Form Validation
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|max:255',
'body' => 'required',
'published_at' => 'nullable|date',
]);
// The validation passed, continue processing...
Article::create($validated);
return redirect()->route('articles.index');
}
Validation is like a quality control checkpoint that ensures incoming data meets your specifications before processing it. It's similar to how a receptionist might verify that a form has all required fields filled out before passing it to the appropriate department.
Returning Responses
Controllers can return various types of responses:
View Responses
// Return a view
return view('user.profile', ['user' => $user]);
// With flash data
return view('dashboard')->with('status', 'Profile updated!');
JSON Responses
// Return JSON data (automatically sets Content-Type header)
return response()->json([
'name' => 'John',
'email' => 'john@example.com'
]);
// With status code
return response()->json(['error' => 'Unauthorized'], 401);
File Downloads
// File download
return response()->download($pathToFile);
// File download with custom name
return response()->download($pathToFile, 'report.pdf');
// Display file in browser
return response()->file($pathToFile);
Redirects
// Redirect to named route
return redirect()->route('login');
// With parameters
return redirect()->route('users.show', ['id' => 1]);
// Redirect to controller action
return redirect()->action([UserController::class, 'index']);
// Redirect back with input
return back()->withInput();
// Redirect with flash data
return redirect('dashboard')->with('status', 'Profile updated!');
The variety of response types is like having different communication channels for different purposes - sometimes you need to send a document (view), sometimes structured data (JSON), sometimes a file, and sometimes you need to direct someone elsewhere (redirect).
API Resource Controllers
For API development, Laravel provides specialized resource controllers that focus on JSON responses:
// Create an API resource controller
php artisan make:controller API/ProductController --api
// Register API resource routes
Route::apiResource('products', ProductController::class);
API resource controllers include only the methods needed for API interactions (index, store, show, update, destroy) and omit the view-related methods (create, edit).
This is like having specialized communication protocols for machine-to-machine interaction (APIs) versus human-facing interfaces.
Resource Collections
// Register multiple API resources at once
Route::apiResources([
'products' => ProductController::class,
'categories' => CategoryController::class,
'tags' => TagController::class,
]);
Route Caching
In production, you can cache your routes for better performance:
php artisan route:cache
This compiles all routes into a single file that the framework loads faster than it would parsing multiple route files.
Route caching is like preparing a roadmap in advance rather than consulting individual maps for each journey - it's more efficient but less flexible once created.
Remember to clear the cache when you modify routes:
php artisan route:clear
Important note: Route caching does not work with Closure-based routes, only controller routes.
Advanced Routing Techniques
Rate Limiting
// Limit a route to 60 requests per minute per user
Route::middleware(['throttle:60,1'])->group(function () {
Route::get('/api/search', [SearchController::class, 'index']);
});
Subdomain Routing
// Match specific subdomain
Route::domain('{account}.example.com')->group(function () {
Route::get('/', function ($account) {
return "Dashboard for {$account}";
});
});
Route Caching
// Cache response for 60 seconds
Route::get('/users', [UserController::class, 'index'])->middleware('cache.headers:60');
These advanced techniques are like specialized traffic management systems for high-volume roads - they help keep your application running smoothly under various conditions.
Real-World Routing and Controller Examples
E-commerce Product Catalog
// routes/web.php
Route::prefix('products')->name('products.')->group(function () {
Route::get('/', [ProductController::class, 'index'])->name('index');
Route::get('/category/{category}', [ProductController::class, 'category'])->name('category');
Route::get('/{product}', [ProductController::class, 'show'])->name('show');
});
// ProductController.php
class ProductController extends Controller
{
public function index(Request $request)
{
$products = Product::query();
if ($search = $request->input('search')) {
$products->where('name', 'like', "%{$search}%");
}
$products = $products->paginate(12);
return view('products.index', compact('products'));
}
public function category(Category $category)
{
$products = $category->products()->paginate(12);
return view('products.category', compact('category', 'products'));
}
public function show(Product $product)
{
$relatedProducts = $product->category->products()
->where('id', '!=', $product->id)
->limit(4)
->get();
return view('products.show', compact('product', 'relatedProducts'));
}
}
User Authentication Flow
// routes/web.php
Route::middleware('guest')->group(function () {
Route::get('/login', [AuthController::class, 'showLogin'])->name('login');
Route::post('/login', [AuthController::class, 'login']);
Route::get('/register', [AuthController::class, 'showRegister'])->name('register');
Route::post('/register', [AuthController::class, 'register']);
});
Route::middleware('auth')->group(function () {
Route::post('/logout', [AuthController::class, 'logout'])->name('logout');
Route::get('/profile', [ProfileController::class, 'show'])->name('profile');
Route::put('/profile', [ProfileController::class, 'update'])->name('profile.update');
});
// AuthController.php
class AuthController extends Controller
{
public function showLogin()
{
return view('auth.login');
}
public function login(Request $request)
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (Auth::attempt($credentials, $request->filled('remember'))) {
$request->session()->regenerate();
return redirect()->intended('/dashboard');
}
return back()->withErrors([
'email' => 'The provided credentials do not match our records.',
])->withInput($request->only('email', 'remember'));
}
// Other methods...
}
Best Practices
Routing Best Practices
- Use Semantic URLs - Create URLs that describe resources, not actions (/users/1 not /get-user/1)
- Group Related Routes - Organize routes logically using groups and prefixes
- Name Your Routes - Give descriptive names to all routes you'll reference in your code
- Use Resource Routes - Leverage resource controllers for CRUD operations
- Avoid Closure Routes in Production - Use controller methods instead of closures to enable route caching
- Use Route Model Binding - Let Laravel handle model resolution from route parameters
Controller Best Practices
- Keep Controllers Thin - Move business logic to services or models
- Follow Single Responsibility Principle - Each controller should focus on a specific resource or concern
- Use Form Requests for Validation - Create dedicated form request classes for complex validation rules
- Return Appropriate Status Codes - Use HTTP status codes correctly, especially in APIs
- Use Dependency Injection - Let Laravel resolve dependencies automatically
- Apply Middleware in Constructors - Set controller-wide middleware in the constructor
Practice Activity
Blog Routing System
Create a complete routing system for a blog application with the following features:
- Public routes for listing and viewing posts
- Admin routes for managing posts, protected by authentication
- API routes for accessing post data programmatically
- Implement route model binding for posts
Advanced Controller Implementation
Create controllers for your blog with:
- Resource controllers for posts and categories
- Proper validation for creating and updating posts
- Authentication middleware for admin actions
- Dependency injection for repositories or services
Real-world Feature Implementation
Implement a search feature for your blog that:
- Accepts search terms from a form
- Filters posts by title and content
- Paginates results
- Highlights search terms in results
- Provides proper feedback when no results are found
Summary
- Routes define how your application responds to HTTP requests at specific URLs
- Controllers organize related request handling logic in a single class
- Route parameters capture dynamic segments of URLs for use in your application
- Resource controllers provide a standardized approach to CRUD operations
- Middleware can filter requests before they reach your controllers
- Laravel offers various response types including views, JSON, files, and redirects
- Route model binding automatically resolves Eloquent models from route parameters
In the next lecture, we'll explore Blade templating and views, building on our understanding of the Laravel request lifecycle.