Middleware

Laravel · Practical · 12 min

Authentication, CSRF, rate limiting and logging all run as middleware around your controllers.

Middleware wraps request handling: each layer receives the request, can stop it early (redirect, 403, 429) or pass it on with $next($request), and can modify the response on the way back. Requests flow through the stack like an onion.

Generate one with php artisan make:middleware EnsureUserIsSubscribed:

public function handle(Request $request, Closure $next): Response
{
    if (! $request->user()?->subscribed()) {
        return redirect('/billing');
    }
    return $next($request);
}

Code before $next() runs on the way in; code after it can change the response.

Register and apply it:

  • On routes: ->middleware(EnsureUserIsSubscribed::class), or a short alias defined in bootstrap/app.php with $middleware->alias(['subscribed' => ...]).
  • Globally: $middleware->append(...) in bootstrap/app.php, or add to a group with $middleware->web(append: [...]).
  • Parameters: ->middleware('role:editor') passes 'editor' as an extra argument to handle.

Laravel ships middleware for the web group (sessions, cookies, CSRF verification) and common aliases: auth, guest, verified, throttle:60,1, can:update,post and signed.

A terminable middleware defines terminate($request, $response), which runs after the response is sent, for logging or cleanup that shouldn't delay the user.

Example

<?php
// app/Http/Middleware/AddRequestId.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class AddRequestId
{
    public function handle(Request $request, Closure $next): Response
    {
        $id = $request->header('X-Request-Id') ?? (string) Str::uuid();
        $request->attributes->set('request_id', $id);      // before
        $response = $next($request);
        $response->headers->set('X-Request-Id', $id);       // after
        return $response;
    }
}
// bootstrap/app.php
// ->withMiddleware(function (Middleware $middleware) {
//     $middleware->append(AddRequestId::class);           // every request
//     $middleware->alias(['subscribed' => EnsureUserIsSubscribed::class]);
// })
// routes/web.php: Route::get('/reports', ...)->middleware(['auth', 'subscribed', 'throttle:30,1']);

Remember

  • Middleware runs before and after the handler around $next($request)
  • Register globally, in groups or by alias in bootstrap/app.php
  • Built-ins include auth, throttle, can and signed

Watch out for

  • Forgetting to return $next($request), which ends the request with an empty response.

Check yourself

  1. In a middleware, where do you put code that must modify the outgoing response?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: laravel.com/docs/12.x/middleware