Desarrollo Web 5-8 minutos

Middleware en Laravel 13: crea, registra y asigna middleware personalizado

Diego Cortés
Diego Cortés
Full Stack Developer & SEO Specialist
Compartir:
Middleware en Laravel 13: crea, registra y asigna middleware personalizado

Cada petición HTTP que entra en tu aplicación pasa por una pila de middleware antes de llegar a la ruta: autenticación, idioma y seguridad son todos middleware, y en Laravel 13 se registran en bootstrap/app.php con withMiddleware(). Esta guía te enseña a crear los tuyos propios con código real.

Qué es un middleware y dónde encaja en Laravel 13

Una capa entre la petición HTTP y la ruta

Un middleware es un mecanismo para inspeccionar y filtrar las peticiones HTTP que entran en tu aplicación antes de que lleguen al controlador. El ejemplo clásico de la documentación oficial es verificar que el usuario está autenticado antes de permitirle acceder a una ruta protegida: si no lo está, se le redirige al login; si lo está, la petición continúa su camino. Pero el mismo patrón sirve para muchísimo más: limitar el número de peticiones por minuto (throttle), añadir cabeceras de seguridad, detectar el idioma del visitante o activar el modo mantenimiento.

Antes y después de la respuesta: el método handle() y $next

Todo middleware define un método handle(Request $request, Closure $next). Todo lo que escribas antes de return $next($request) se ejecuta cuando la petición entra, y todo lo que escribas después se ejecuta cuando la respuesta ya ha sido generada por el controlador. Eso te permite tanto bloquear una petición (devolviendo una respuesta directamente, sin llamar a $next) como modificar la respuesta de salida, por ejemplo añadiendo una cabecera. Esa doble capacidad es lo que hace al middleware tan potente: una sola clase puede actuar de filtro de entrada y de decorador de salida.

Crear un middleware con artisan

php artisan make:middleware y la estructura de la clase

Laravel genera la estructura por ti con un solo comando:

php artisan make:middleware EnsureLocaleIsValid

El comando crea la clase en app/Http/Middleware/EnsureLocaleIsValid.php con el esqueleto de handle() listo para rellenar:

<?php

namespace App\Http\Middleware;

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

class EnsureLocaleIsValid
{
    public function handle(Request $request, Closure $next): Response
    {
        return $next($request);
    }
}

Por convención, el código que corre antes de la petición va antes de return $next($request) y el que corre después va después, manipulando la respuesta que devuelve la llamada. Nada más.

Middleware inline con Closure directamente en las rutas

Para lógica pequeña y puntual no hace falta crear una clase. Puedes pasar un Closure como middleware directamente en la definición de la ruta:

Route::get('/panel', function () {
    // ...
})->middleware(function (Request $request, Closure $next) {
    if ($request->user()?->isAdmin() !== true) {
        abort(403);
    }

    return $next($request);
});

Es práctico para prototipos, pero si la lógica se repite en varias rutas, conviene convertirla en una clase.

Registrar y asignar middleware en Laravel 13

bootstrap/app.php y withMiddleware(): el adiós a Kernel.php

Desde Laravel 11 ya no existe app/Http/Kernel.php, y Laravel 13 mantiene ese enfoque. Toda la configuración de middleware vive en bootstrap/app.php dentro del método withMiddleware():

->withMiddleware(function (Middleware $middleware) {
    // aquí se configura todo
})

Ahí defines los middleware globales, amplías los grupos web y api, y registras los alias para tus clases.

Global: append y prepend para todas las peticiones

Un middleware global corre en todas las peticiones de la aplicación, sin excepción. Se añade con append() (al final de la pila) o con prepend() (al principio):

->withMiddleware(function (Middleware $middleware) {
    $middleware->append(AddSecurityHeaders::class);
})

Úsalo con criterio: cada middleware global añade trabajo a cada petición, y una pila enorme es difícil de depurar.

Grupos web y api: appendToGroup

Laravel agrupa el middleware por defecto en dos grupos: web (para rutas con sesión y cookies) y api (para APIs con throttle y CORS). Puedes añadir middleware propios a cualquiera de ellos con appendToGroup():

->withMiddleware(function (Middleware $middleware) {
    $middleware->appendToGroup('web', EnsureLocaleIsValid::class);
})

Así, el middleware se aplica a todas las rutas de ese grupo sin tener que mencionarlo en cada una.

Asignar a rutas y grupos de rutas: ->middleware()

Para aplicar un middleware solo a rutas concretas, se asigna en la propia definición, con el nombre de la clase o con un alias:

Route::get('/perfil', [ProfileController::class, 'edit'])
    ->middleware('auth');

Route::prefix('/admin')->middleware(['auth', 'role:admin'])->group(function () {
    Route::resource('posts', PostController::class);
});

El método acepta un string o un array, y funciona tanto en rutas individuales como en grupos de rutas, lo que permite combinar varios middleware en una sola línea.

Aliases: nombres cortos para tus clases

Laravel trae aliases por defecto para su middleware integrado: auth, guest, signed, throttle, verified y otros. Los tuyos propios se registran en withMiddleware() con alias():

->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'locale' => EnsureLocaleIsValid::class,
        'role' => EnsureUserHasRole::class,
    ]);
})

A partir de ese momento puedes usar ->middleware('locale') en cualquier ruta, con un nombre corto y legible.

Middleware con parámetros

Pasar argumentos tras $next: middleware('role:admin')

Los middleware pueden recibir parámetros adicionales con la sintaxis alias:parametro. El valor llega al handle() como argumento extra después de $next:

public function handle(Request $request, Closure $next, string $role): Response
{
    if (! $request->user() || ! $request->user()->hasRole($role)) {
        abort(403);
    }

    return $next($request);
}
Route::get('/admin/posts', ...)->middleware('role:admin');

Es el patrón típico para roles y permisos, y permite reutilizar la misma clase con distintos valores en distintas rutas, como role:admin y role:editor.

Middleware terminable: código después de enviar la respuesta

El método terminate() y el requisito de FastCGI

Si tu middleware define un método terminate($request, $response), ese código se ejecuta después de que la respuesta se haya enviado al navegador, siempre que el servidor use FastCGI (como PHP-FPM). Es ideal para tareas que no deben retrasar la respuesta:

public function terminate(Request $request, Response $response): void
{
    if ($response->getStatusCode() === 200 && $request->path() !== 'health') {
        Log::info('Peticion servida', [
            'path' => $request->path(),
            'duration_ms' => round((microtime(true) - LARAVEL_START) * 1000),
        ]);
    }
}

Piensa en él como un afterCommit para la respuesta: logging, limpieza de recursos o avisos que no merece la pena hacer esperar al usuario.

Tres middleware útiles para un blog en Laravel 13

Detección de idioma desde el primer segmento de la URL

Un blog bilingüe como blenderdeluxe necesita saber qué idioma está pidiendo el visitante antes de generar la vista. Un middleware propio puede leer el primer segmento de la URL y fijar la locale de la aplicación:

public function handle(Request $request, Closure $next): Response
{
    $locale = $request->segment(1);

    if (in_array($locale, ['es', 'en'], true)) {
        app()->setLocale($locale);
    }

    return $next($request);
}

Antes, esa comprobación se repetía en cada controlador; con el middleware, la lógica transversal vive en un solo sitio.

Cabeceras de seguridad y CORS controlado

Otra candidata perfecta es un middleware global que añada cabeceras de seguridad a todas las respuestas:

public function handle(Request $request, Closure $next): Response
{
    $response = $next($request);

    $response->headers->set('X-Content-Type-Options', 'nosniff');
    $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
    $response->headers->set('X-Frame-Options', 'SAMEORIGIN');

    return $response;
}

Y si expones una API, el middleware de CORS que Laravel aplica por defecto al grupo api es un ejemplo perfecto de cómo el framework resuelve con middleware lo que antes era configuración de servidor.

Log de peticiones lento y modo mantenimiento propios

El mismo patrón sirve para medir el rendimiento: un middleware que cronometra la petición y registra las que superan un umbral, o un modo mantenimiento propio que responda 503 con una vista personalizada durante los despliegues. Todos son variaciones de la misma idea: interceptar, decidir y dejar pasar o responder.

Orden de ejecución y buenas prácticas

Cómo se ordena la pila y por qué importa

El orden de la pila es el orden de ejecución: una petición entra por el primer middleware y sale por el último, y la respuesta hace el recorrido inverso. Por eso el orden de append/prepend y de los grupos importa: un middleware de autenticación debe ir antes que uno que asume que el usuario existe. Visualízalo como capas de una cebolla: la petición atraviesa cada capa hacia dentro y la respuesta vuelve hacia fuera.

Errores comunes: alias sin registrar, withoutMiddleware y globales

El fallo más habitual es usar un alias que no has registrado en withMiddleware(): Laravel lanza una excepción de clase no encontrada. El segundo clásico es intentar quitar un middleware global con withoutMiddleware(): ese método solo elimina middleware de rutas concretas, nunca los globales, y usarlo con ellos no tiene efecto. Nombra tus middleware por su intención (Ensure..., Set..., Log...), registra alias cortos y no abuses de los globales: cada uno corre en todas las peticiones y una pila gigante acaba costando caro de mantener.

Conclusión

El middleware en Laravel 13 es el lugar natural para toda la lógica transversal: autenticación, idioma, seguridad y logging, sin ensuciar los controladores. Créalos con make:middleware, regístralos en bootstrap/app.php con withMiddleware() y asígnalos por alias a rutas o grupos. Si te interesa el flujo de autenticación completo, mira la guía de login social con Socialite o la de passkeys con WebAuthn. Y sigue leyendo el blog: cada semana hay una guía nueva de desarrollo web con Laravel 13.

Categorías