Laravel Middleware Guide

Create, register, and apply Laravel middleware: HTTP filters, authentication guards, throttling, terminable middleware, and group configuration.

1. Creating Middleware

// php artisan make:middleware EnsureApiKey

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureApiKey
{
    public function handle(Request $request, Closure $next): Response
    {
        $key = $request->header('X-API-Key') ?? $request->query('api_key');

        if (! $key || ! ApiKey::where('key', $key)->where('is_active', true)->exists()) {
            return response()->json(['error' => 'Invalid API key'], 403);
        }

        return $next($request);
    }
}

// Middleware with parameters
class RequireRole
{
    public function handle(Request $request, Closure $next, string ...$roles): Response
    {
        if (! $request->user() || ! in_array($request->user()->role, $roles)) {
            abort(403, 'Insufficient permissions');
        }
        return $next($request);
    }
}

// Usage: Route::middleware(['auth', 'role:admin,editor'])->group(...);

2. Registering Middleware (Laravel 11+)

// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        // Global middleware
        $middleware->append(LogRequests::class);
        $middleware->prepend(TrustProxies::class);

        // Alias
        $middleware->alias([
            'role'    => RequireRole::class,
            'api.key' => EnsureApiKey::class,
            'verified' => EnsureEmailIsVerified::class,
        ]);

        // Add to existing group
        $middleware->appendToGroup('web', [
            ShareDataWithViews::class,
        ]);
        $middleware->appendToGroup('api', [
            EnsureApiKey::class,
        ]);

        // Middleware priority
        $middleware->priority([
            TrustProxies::class,
            HandleCors::class,
            StartSession::class,
            Authenticate::class,
        ]);
    })->create();

3. Applying Middleware to Routes

use Illuminate\Support\Facades\Route;

// Single route
Route::get('/admin', AdminController::class)->middleware('auth', 'role:admin');

// Route group
Route::middleware(['auth', 'verified'])->group(function () {
    Route::resource('articles', ArticleController::class);
    Route::get('/dashboard', DashboardController::class);
});

// Nested groups
Route::prefix('admin')->middleware(['auth', 'role:admin'])->name('admin.')->group(function () {
    Route::resource('users', AdminUserController::class);
    Route::resource('settings', SettingController::class);
});

// Controller middleware (Laravel 11 constructor middleware)
class ArticleController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth')->except(['index', 'show']);
        $this->middleware('throttle:10,1')->only(['store', 'update', 'destroy']);
    }
}

4. Terminable Middleware

class LogResponseTime implements TerminableMiddleware
{
    private float $startTime;

    public function handle(Request $request, Closure $next): Response
    {
        $this->startTime = microtime(true);
        return $next($request);
    }

    // Called after response is sent to client
    public function terminate(Request $request, Response $response): void
    {
        $duration = (microtime(true) - $this->startTime) * 1000;
        RequestLog::create([
            'method'   => $request->method(),
            'path'     => $request->path(),
            'status'   => $response->getStatusCode(),
            'duration' => round($duration),
            'ip'       => $request->ip(),
        ]);
    }
}

5. Common Middleware Patterns

// Force HTTPS in production
class ForceHttps
{
    public function handle(Request $request, Closure $next): Response
    {
        if (! $request->secure() && app()->environment('production')) {
            return redirect()->secure($request->getRequestUri(), 301);
        }
        return $next($request);
    }
}

// Set locale from URL segment
class SetLocale
{
    public function handle(Request $request, Closure $next): Response
    {
        $locale = $request->segment(1);
        if (in_array($locale, config('app.supported_locales'))) {
            app()->setLocale($locale);
        }
        return $next($request);
    }
}

6. Built-in Middleware Reference

Class / AliasPurpose
authAuthenticate user
auth:apiAuthenticate via API guard
throttle:60,1Rate limit (60 req/min)
verifiedEmail must be verified
signedValidate signed URL
can:update,articleAuthorization gate
cache.headersSet HTTP cache headers
ValidateSignatureSigned routes
HandleCorsCross-Origin headers