Introduction to Middleware
Middleware provides a convenient mechanism for inspecting and filtering HTTP requests entering your application. Think of middleware as layers that requests pass through before reaching your application logic. Common uses include authentication, logging, CORS handling, and request/response modification.
Note: Middleware acts as a "bridge" between the HTTP request and your application. Each middleware can pass the request along to the next middleware in the stack, or terminate the request early.
How Middleware Works
When a request hits your Laravel application, it passes through a series of middleware layers:
Request → Middleware 1 → Middleware 2 → Middleware 3 → Controller
↓
Response ← Middleware 3 ← Middleware 2 ← Middleware 1 ← Controller
Each middleware can:
- Inspect the incoming request before it reaches the controller
- Modify the request (add headers, change data, etc.)
- Terminate the request early (redirect, return error, etc.)
- Inspect or modify the response after the controller executes
Laravel's Built-in Middleware
Laravel includes several middleware out of the box:
// app/Http/Kernel.php
protected $middleware = [
// Global middleware (runs on every request)
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\Illuminate\Foundation\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
protected $middlewareGroups = [
'web' => [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
protected $middlewareAliases = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
Tip: In Laravel 11+, middleware configuration has moved to bootstrap/app.php for a cleaner structure. The concepts remain the same.
Creating Custom Middleware
Use the Artisan command to generate middleware:
# Create middleware
php artisan make:middleware CheckAge
# Creates: app/Http/Middleware/CheckAge.php
Here's a basic middleware example that checks if the user is over 18:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CheckAge
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next)
{
// Check condition before passing to next middleware
if ($request->age < 18) {
return redirect('home')->with('error', 'You must be 18 or older');
}
// Pass request to the next middleware/controller
return $next($request);
}
}
Before & After Middleware
Middleware can run logic before or after the request is handled:
<?php
// Before Middleware - logic runs BEFORE request reaches controller
class BeforeMiddleware
{
public function handle(Request $request, Closure $next)
{
// Perform action before request
Log::info('Request received: ' . $request->path());
return $next($request);
}
}
// After Middleware - logic runs AFTER controller returns response
class AfterMiddleware
{
public function handle(Request $request, Closure $next)
{
// Get response from next middleware/controller
$response = $next($request);
// Perform action after response is generated
$response->header('X-Custom-Header', 'Value');
return $response;
}
}
// Combined - run logic both before and after
class CombinedMiddleware
{
public function handle(Request $request, Closure $next)
{
// Before logic
Log::info('Before: ' . $request->path());
$response = $next($request);
// After logic
Log::info('After: Response sent');
return $response;
}
}
Registering Middleware
Register middleware in app/Http/Kernel.php (Laravel 10 and below) or bootstrap/app.php (Laravel 11+):
<?php
// app/Http/Kernel.php (Laravel 10)
// 1. Global Middleware (runs on every request)
protected $middleware = [
\App\Http\Middleware\CheckAge::class,
];
// 2. Middleware Groups (web, api)
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\CheckAge::class,
],
];
// 3. Route Middleware (assigned to specific routes)
protected $middlewareAliases = [
'age' => \App\Http\Middleware\CheckAge::class,
'admin' => \App\Http\Middleware\IsAdmin::class,
];
Applying Middleware to Routes
Apply middleware to routes in several ways:
<?php
use Illuminate\Support\Facades\Route;
// Single middleware
Route::get('/dashboard', [DashboardController::class, 'index'])
->middleware('auth');
// Multiple middleware
Route::get('/admin', [AdminController::class, 'index'])
->middleware(['auth', 'admin']);
// Middleware group
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
Route::get('/profile', [ProfileController::class, 'show']);
});
// Exclude middleware
Route::middleware(['web'])->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class])->group(function () {
Route::post('/webhook', [WebhookController::class, 'handle']);
});
// Apply in controller constructor
class AdminController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('admin')->only(['create', 'edit']);
$this->middleware('log')->except(['index']);
}
}
Middleware Parameters
Pass parameters to middleware for flexible logic:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CheckRole
{
/**
* Handle request with role parameter.
*/
public function handle(Request $request, Closure $next, string $role)
{
if (!$request->user()->hasRole($role)) {
abort(403, "Unauthorized. Required role: {$role}");
}
return $next($request);
}
}
// Usage in routes
Route::get('/admin/users', [UserController::class, 'index'])
->middleware('role:admin');
Route::get('/editor/posts', [PostController::class, 'index'])
->middleware('role:editor');
// Multiple parameters (separated by comma)
class CheckPermissions
{
public function handle(Request $request, Closure $next, string ...$permissions)
{
foreach ($permissions as $permission) {
if (!$request->user()->can($permission)) {
abort(403, "Missing permission: {$permission}");
}
}
return $next($request);
}
}
// Usage
Route::get('/posts/create', [PostController::class, 'create'])
->middleware('permissions:create-post,publish-post');
Note: Middleware parameters are separated by colons in route definitions, and commas within the parameter string separate multiple values.
Practical Middleware Examples
1. Logging Middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class LogRequests
{
public function handle(Request $request, Closure $next)
{
$start = microtime(true);
// Log incoming request
Log::info('Request started', [
'method' => $request->method(),
'url' => $request->fullUrl(),
'ip' => $request->ip(),
'user_id' => $request->user()?->id,
]);
$response = $next($request);
// Log response time
$duration = microtime(true) - $start;
Log::info('Request completed', [
'duration' => round($duration * 1000, 2) . 'ms',
'status' => $response->status(),
]);
return $response;
}
}
2. API Key Authentication Middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ValidateApiKey
{
public function handle(Request $request, Closure $next)
{
$apiKey = $request->header('X-API-Key');
if (!$apiKey || $apiKey !== config('app.api_key')) {
return response()->json([
'error' => 'Invalid or missing API key'
], 401);
}
return $next($request);
}
}
// Usage
Route::middleware(['api.key'])->group(function () {
Route::get('/api/data', [ApiController::class, 'getData']);
});
3. Force JSON Response Middleware (for APIs)
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ForceJsonResponse
{
public function handle(Request $request, Closure $next)
{
// Force Accept: application/json header
$request->headers->set('Accept', 'application/json');
return $next($request);
}
}
4. Localization Middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
class SetLocale
{
public function handle(Request $request, Closure $next)
{
// Check locale from query parameter, session, or user preference
$locale = $request->query('lang')
?? $request->session()->get('locale')
?? $request->user()?->locale
?? config('app.locale');
// Validate locale
if (in_array($locale, ['en', 'ar', 'fr'])) {
App::setLocale($locale);
$request->session()->put('locale', $locale);
}
return $next($request);
}
}
Terminable Middleware
Sometimes you need to perform work after the response has been sent to the browser. Terminable middleware allows this:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class LogActivity
{
public function handle(Request $request, Closure $next)
{
return $next($request);
}
/**
* Runs after response is sent to browser.
*/
public function terminate(Request $request, $response): void
{
// Heavy logging operation that doesn't block response
DB::table('activity_log')->insert([
'user_id' => $request->user()?->id,
'path' => $request->path(),
'method' => $request->method(),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'created_at' => now(),
]);
}
}
Tip: Use terminable middleware for tasks like logging, analytics, or cleanup that don't need to delay the response to the user.
Middleware Priorities
Control the order in which middleware executes by defining priorities:
<?php
// app/Http/Kernel.php
protected $middlewarePriority = [
\Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
Exercise 1: Create Admin Middleware
Create middleware that checks if the authenticated user has an "is_admin" flag set to true.
- Run
php artisan make:middleware IsAdmin
- Check if user is authenticated
- Verify user has is_admin property set to true
- Return 403 error if not admin
- Register middleware in Kernel.php as 'admin'
- Apply to admin routes
Exercise 2: Request Throttling Middleware
Create custom middleware that limits users to 10 requests per minute on specific routes.
- Create middleware with
php artisan make:middleware ThrottleCustom
- Use Cache facade to store request count per user/IP
- Increment counter on each request
- Return 429 Too Many Requests if limit exceeded
- Add X-RateLimit headers to response
- Test with multiple rapid requests
Exercise 3: Response Time Middleware
Create middleware that adds an X-Response-Time header showing how long the request took to process.
- Create middleware with timing logic
- Record start time before passing request
- Calculate duration after getting response
- Add X-Response-Time header with milliseconds
- Optionally log slow requests (>1000ms)
- Apply globally to web middleware group
Warning: Be careful with global middleware - they run on every request and can impact performance. Only add middleware to the global stack if truly necessary.
Summary
In this lesson, you learned:
- What middleware is and how it works in Laravel
- Laravel's built-in middleware types (global, groups, route)
- Creating custom middleware with Artisan
- Before, after, and combined middleware patterns
- Registering and applying middleware to routes
- Passing parameters to middleware
- Practical middleware examples (logging, API keys, localization)
- Terminable middleware for post-response tasks
- Middleware execution priorities
Next Lesson: We'll explore file storage and uploads in Laravel, including local storage, S3, and file validation.