Middleware is a filtering layer that every HTTP request passes through before reaching the controller, and every response passes through before it is sent back to the user. Think of it as a checkpoint: you can verify that a user is logged in, check permissions, write logs, or switch the application language — all in one place, without cluttering your controllers. This article explains how middleware works in Laravel 10/11 with real, practical examples.
1. Where Middleware Sits in the Request Lifecycle
When a request arrives, Laravel runs it through a chain of middleware in layers (like an onion). The flow is roughly: Request in → global middleware → group/route middleware → controller → response → middleware (on the way back) → Response out. Because of this layered structure, a single middleware can run code before the request is forwarded and after the response is created.
2. Creating Middleware
Use the following artisan command to scaffold a middleware:
php artisan make:middleware EnsureUserIsActive
A new file appears at app/Http/Middleware/EnsureUserIsActive.php with a single handle() method. The $next parameter is a closure that forwards the request to the next layer:
public function handle(Request $request, Closure $next): Response
{
if (! $request->user() || ! $request->user()->is_active) {
return redirect('/login')
->with('error', 'Your account is inactive.');
}
return $next($request);
}
The key insight: if you call return $next($request), the request continues to the controller. If you return redirect() or abort() first, the request stops right here.
3. Registering Middleware
Registration differs between Laravel 10 and 11. In Laravel 11, everything is registered in bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
// alias for per-route use
$middleware->alias([
'active' => \App\Http\Middleware\EnsureUserIsActive::class,
]);
// global middleware (runs for every request)
$middleware->append(\App\Http\Middleware\LogRequests::class);
})
In Laravel 10, registration happens in app/Http/Kernel.php via the $middlewareAliases, $middleware (global), and $middlewareGroups properties.
Applying to Routes
Once aliased, attach it to a route or a route group:
Route::get('/dashboard', [DashboardController::class, 'index'])
->middleware(['auth', 'active']);
Route::middleware(['auth', 'active'])->group(function () {
Route::resource('products', ProductController::class);
});
4. Before vs After Middleware
The difference is when your logic runs relative to $next($request).
Before middleware runs code before the request is forwarded — ideal for permission checks:
public function handle(Request $request, Closure $next): Response
{
// this runs BEFORE the controller
Log::info('Incoming request: '.$request->path());
return $next($request);
}
After middleware runs code after the response is built — ideal for adding headers or logging the status:
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// this runs AFTER the controller
$response->headers->set('X-App-Version', '1.0');
return $response;
}
5. Middleware with Parameters
Middleware can accept arguments — the most common pattern is a role gate. Add a parameter after $next:
public function handle(Request $request, Closure $next, string $role): Response
{
if ($request->user()->role !== $role) {
abort(403, 'You do not have access.');
}
return $next($request);
}
Pass the parameter from the route using a colon:
Route::get('/admin', [AdminController::class, 'index'])
->middleware('role:admin');
Need multiple values? Separate them with commas: middleware('role:admin,editor') then capture with ...$roles.
6. Real Example: A Locale Middleware
Middleware is great for switching the application language based on a user preference or URL parameter. A simple example:
public function handle(Request $request, Closure $next): Response
{
$locale = $request->query('lang', session('locale', 'id'));
if (in_array($locale, ['id', 'en'])) {
app()->setLocale($locale);
session(['locale' => $locale]);
}
return $next($request);
}
Register it as global middleware or inside the web group so every page respects the user's language choice.
7. Common Mistakes to Avoid
- Forgetting
return $next($request): the request never reaches the controller and you only see a blank page. - Putting heavy logic in global middleware: since it runs on every request, database queries there can slow down the whole app.
- Wrong order: the auth middleware must run before any middleware that accesses
$request->user(), otherwise it will be null.
By understanding middleware, you gain a powerful tool for separating security, logging, and request transformation concerns from your business logic. Your controllers stay lean, and cross-cutting rules live in one place that is easy to test.