Middleware: Concept and Building Your Own
In this article, we'll explain how middleware fits into Laravel's request lifecycle, walk through some commonly used built-in middleware, and show you how to build a custom middleware of your own from scratch.
Introduction
Every request that enters a Laravel application doesn't go straight to your controller — it often passes through several layers of checks and processing first. Is the user logged in? Is their session still valid? Should this request even be allowed through? This layered filtering system is exactly what middleware is designed for.
Middleware acts like a series of checkpoints that a request must pass through before reaching your controller (and sometimes after, on its way back out as a response). Laravel ships with several useful middleware out of the box, but you can also create your own custom middleware to handle logic specific to your application.
In this article, we'll explain how middleware fits into Laravel's request lifecycle, walk through some commonly used built-in middleware, and show you how to build a custom middleware of your own from scratch.
What is Middleware, Conceptually?
Think of middleware as a series of layers a request passes through, one at a time, before it ever reaches your controller. Each layer can inspect the request, modify it, reject it entirely, or simply let it continue to the next layer.
A helpful mental model: imagine airport security checkpoints. Before you reach your gate (the controller), you pass through several checks — ID verification, baggage scanning, boarding pass validation. Each checkpoint can either let you through or stop you right there. Middleware works the same way for HTTP requests.
Built-In Middleware You've Already Used
Laravel includes several middleware by default, some of which you've likely already encountered without realizing it:
auth— Ensures a user is logged in before allowing access to a route; redirects to the login page otherwise.guest— The opposite ofauth; only allows access to users who are NOT logged in (commonly used on login/register pages).verified— Ensures a user has verified their email address before proceeding.throttle— Limits how many requests a user can make within a given time frame, helping prevent abuse.
You apply middleware to a route like this:
php
Route::get('/dashboard', [DashboardController::class, 'index'])->middleware('auth');Or to a group of routes, as we covered in the article on route groups:
php
Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
Route::get('/profile', [ProfileController::class, 'show']);
});Building Your Own Middleware
Let's walk through creating a simple custom middleware that logs the timestamp of every incoming request — a common example that illustrates the core concept clearly.
Step 1: Generate the Middleware
php
php artisan make:middleware LogRequestTimeThis creates a new file at app/Http/Middleware/LogRequestTime.php:
php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class LogRequestTime
{
public function handle(Request $request, Closure $next)
{
return $next($request);
}
}Step 2: Add Your Logic
The handle() method is where your custom logic goes. The $next($request) call passes the request along to the next layer (eventually reaching your controller). Anything you write before that call runs before the controller; anything after it runs after the response has been generated.
php
public function handle(Request $request, Closure $next)
{
\Log::info('Request received at: ' . now());
$response = $next($request);
\Log::info('Response sent at: ' . now());
return $response;
}Step 3: Register the Middleware
For newer Laravel versions (11 and above), middleware is registered inside bootstrap/app.php:
php
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'log.time' => \App\Http\Middleware\LogRequestTime::class,
]);
})For older versions, this is typically done inside app/Http/Kernel.php, within the $routeMiddleware array.
Step 4: Apply It to a Route
php
Route::get('/dashboard', [DashboardController::class, 'index'])->middleware('log.time');Now, every time that route is accessed, your middleware runs first, logs the request time, lets the controller do its work, and then logs the response time afterward.
A More Practical Example: Blocking Non-Admin Users
Here's a more realistic example — a middleware that only allows users with an is_admin flag to proceed, redirecting everyone else:
php
public function handle(Request $request, Closure $next)
{
if (! $request->user() || ! $request->user()->is_admin) {
return redirect('/')->with('error', 'Unauthorized access.');
}
return $next($request);
}Applied to an admin route group:
php
Route::middleware(['auth', 'admin'])->prefix('admin')->group(function () {
Route::get('/dashboard', [AdminController::class, 'dashboard']);
});Tips for Working with Middleware
Keep middleware focused on one responsibility — a single middleware should handle one specific concern, like authentication or logging, not several unrelated tasks at once.
Always call
$next($request), unless you intentionally want to stop the request (like redirecting unauthorized users) — forgetting this will cause your application to hang.Use middleware groups (
webandapi, defined by Laravel by default) to apply common middleware automatically to entire sets of routes.Order matters when stacking multiple middleware — Laravel runs them in the sequence they're listed, so make sure dependent checks (like
authbeforeadmin) are ordered correctly.Use built-in middleware whenever possible before building your own — Laravel already covers many common use cases like authentication, rate limiting, and CSRF protection.
FAQ About Middleware
1. What's the difference between middleware and validation inside a controller? Middleware handles cross-cutting concerns that apply broadly across many routes (like authentication or logging), while validation typically deals with the specific data submitted in a single request, handled directly in the controller or a form request class.
2. Can I apply multiple middleware to a single route? Yes. You can pass an array of middleware names: ->middleware(['auth', 'verified']), and Laravel will run them in the order listed.
3. What happens if I forget to call $next($request) in my middleware? The request will never reach the controller (or any subsequent middleware), effectively hanging the application without returning any response.
4. Can middleware modify the response, not just the request? Yes. Since $next($request) returns the response, you can inspect or modify it after the controller has run, before returning it to the browser — useful for tasks like adding custom headers.
5. What's the difference between route middleware and global middleware? Route middleware only applies to routes it's explicitly attached to, while global middleware (registered separately) runs on every single request to your application, regardless of the route.
6. Is middleware only used for authentication? No. While authentication is one of the most common use cases, middleware is also used for logging, rate limiting, CORS handling, maintenance mode, localization, and many other cross-cutting concerns.
Conclusion
Middleware gives you a clean, reusable way to handle logic that needs to run before or after a request reaches your controller, without cluttering your controllers themselves with repetitive checks. Whether you're using Laravel's built-in middleware or building your own, understanding this layer of the request lifecycle is key to writing secure, well-organized applications.
In the final article of this category, we'll cover route model binding — a feature that eliminates the need for manual database lookups inside your controllers by automatically resolving models directly from route parameters.
Found this helpful? Share it!