Laravel Framework

Middleware in Laravel

15 min Lesson 12 of 45

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.

  1. Run php artisan make:middleware IsAdmin
  2. Check if user is authenticated
  3. Verify user has is_admin property set to true
  4. Return 403 error if not admin
  5. Register middleware in Kernel.php as 'admin'
  6. Apply to admin routes

Exercise 2: Request Throttling Middleware

Create custom middleware that limits users to 10 requests per minute on specific routes.

  1. Create middleware with php artisan make:middleware ThrottleCustom
  2. Use Cache facade to store request count per user/IP
  3. Increment counter on each request
  4. Return 429 Too Many Requests if limit exceeded
  5. Add X-RateLimit headers to response
  6. 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.

  1. Create middleware with timing logic
  2. Record start time before passing request
  3. Calculate duration after getting response
  4. Add X-Response-Time header with milliseconds
  5. Optionally log slow requests (>1000ms)
  6. 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.