Middleware en Laravel: qué es, cómo crearlo y cuándo usarlo
Un middleware en Laravel es una capa que puede inspeccionar, modificar, permitir o detener una solicitud HTTP antes de que llegue a la lógica principal de la aplicación. Se utiliza para problemas transversales como autenticación, verificación, rate limiting, condiciones de acceso y procesamiento alrededor del request, pero no debería convertirse en un lugar donde almacenar toda la lógica de negocio.
¿Qué es un middleware en Laravel?
Un middleware es una capa situada entre la petición HTTP y la lógica que finalmente procesa esa petición.
Puede examinar el usuario autenticado, headers, parámetros, estado de la cuenta u otras características del request.
Si la solicitud cumple las condiciones, el middleware la entrega a la siguiente capa mediante:
return $next($request);
Si la condición no se cumple, puede devolver una respuesta inmediatamente y evitar que el controller llegue a ejecutarse.
El middleware forma parte del recorrido de una solicitud
Request
Llega una solicitud HTTP.
Middleware
Inspecciona o filtra la solicitud.
Route
Laravel determina el destino correspondiente.
Controller
Coordina la operación principal.
Response
Se genera una respuesta HTTP.
Salida
La respuesta atraviesa nuevamente las capas correspondientes.
Piensa en middleware como capas alrededor de la aplicación
Imagina una solicitud:
GET /panel
Antes de ejecutar el controller, la solicitud puede atravesar varias capas.
Request
↓
Middleware A
↓
Middleware B
↓
Middleware C
↓
Route
↓
Controller
↓
Response
Cada middleware puede permitir que la petición continúe o interrumpir el recorrido.
Ejemplo: usuario no autenticado
GET /panel
↓
auth
↓
¿Usuario autenticado?
↓
NO
↓
No ejecutar PanelController
Esa capacidad evita repetir la misma comprobación dentro de cada controller.
¿Para qué sirve un middleware en Laravel?
Middleware resulta útil cuando una regla está relacionada con el tratamiento de muchas solicitudes, no únicamente con una operación específica del negocio.
Exigir autenticación.
Comprobar que un email esté verificado.
Aplicar rate limiting.
Comprobar determinadas condiciones de acceso.
Añadir información a una respuesta.
Registrar datos relacionados con requests.
Middleware no debería resolver cualquier problema
Que técnicamente puedas ejecutar lógica compleja dentro de una capa no significa que debas hacerlo.
Genera tu primer middleware personalizado
Artisan puede crear la estructura inicial.
php artisan make:middleware EnsureAccountIsActive
La clase quedará normalmente dentro de:
app/Http/Middleware/EnsureAccountIsActive.php
Podemos comprobar si la cuenta del usuario está activa.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
final class EnsureAccountIsActive
{
public function handle(
Request $request,
Closure $next
): Response {
$user = $request->user();
if (
$user !== null
&&
! $user->activo
) {
abort(
403,
'La cuenta está desactivada.'
);
}
return $next($request);
}
}
El middleware comprueba una condición y, si puede continuar, entrega el request a la siguiente capa.
$next($request) es lo que permite que la solicitud siga avanzando
Este fragmento:
return $next($request);
entrega la solicitud a la siguiente capa del pipeline.
En cambio:
return response()->json(
[
'message' =>
'No tienes acceso.'
],
403
);
termina el recorrido en ese punto.
Esa diferencia es la esencia del middleware
Puede actuar como una puerta:
Condición correcta
↓
$next($request)
↓
Continúa
Condición incorrecta
↓
return Response
↓
Se detiene
Puedes asignar una clase middleware directamente a una ruta
<?php
use App\Http\Middleware\EnsureAccountIsActive;
use Illuminate\Support\Facades\Route;
Route::get(
'/panel',
[PanelController::class, 'index']
)->middleware(
EnsureAccountIsActive::class
);
También puedes asignar varias capas a la misma ruta.
Route::get(
'/panel',
[PanelController::class, 'index']
)->middleware([
'auth',
EnsureAccountIsActive::class
]);
Aquí primero necesitamos una identidad autenticada y después comprobamos el estado de esa cuenta.
Un alias evita repetir el nombre completo de una clase
Si utilizas frecuentemente un middleware, puedes registrarle un nombre corto desde:
bootstrap/app.php
<?php
use App\Http\Middleware\EnsureAccountIsActive;
use Illuminate\Foundation\Configuration\Middleware;
->withMiddleware(
function (Middleware $middleware): void {
$middleware->alias([
'account.active' =>
EnsureAccountIsActive::class
]);
}
)
Después la ruta resulta mucho más compacta.
Route::get(
'/panel',
[PanelController::class, 'index']
)->middleware([
'auth',
'account.active'
]);
El alias debería comunicar claramente qué condición representa.
Laravel ya proporciona aliases para necesidades frecuentes
| Alias | Objetivo |
|---|---|
| auth | Exigir autenticación |
| guest | Trabajar con usuarios no autenticados |
| can | Aplicar autorización |
| verified | Exigir verificación de email |
| signed | Validar URLs firmadas |
| throttle | Limitar solicitudes |
| password.confirm | Exigir confirmación reciente de contraseña |
Esto significa que no deberías recrear manualmente capacidades que Laravel ya proporciona de manera coherente.
auth responde a una pregunta sencilla: ¿hay una identidad autenticada?
Route::get(
'/perfil',
[PerfilController::class, 'show']
)->middleware('auth');
No deberías repetir dentro del controller:
if (! auth()->check()) {
// ...
}
en cada método privado de la aplicación.
Middleware permite expresar esa condición en el límite HTTP.
Aprende sesiones, Sanctum, Policies y permisos
Middleware auth es solo una parte del sistema de acceso.
El middleware can conecta el ciclo HTTP con las reglas de autorización
Supongamos que una ruta actualiza un proyecto.
Route::patch(
'/proyectos/{proyecto}',
[ProyectoController::class, 'update']
)->middleware(
'can:update,proyecto'
);
Antes de ejecutar el controller, Laravel puede comprobar si el usuario está autorizado para realizar update sobre ese proyecto.
Esto enlaza middleware con Policies o las reglas de autorización correspondientes.
“Es administrador” y “puede modificar este registro” no siempre significan lo mismo.
Agrupa rutas cuando comparten las mismas condiciones
Imagina un panel con veinte rutas privadas.
No necesitas añadir:
->middleware([
'auth',
'account.active'
]);
veinte veces.
Puedes agruparlas.
Route::middleware([
'auth',
'account.active'
])->group(function () {
Route::get(
'/panel',
[PanelController::class, 'index']
);
Route::get(
'/perfil',
[PerfilController::class, 'show']
);
Route::get(
'/pedidos',
[PedidoController::class, 'index']
);
});
La agrupación comunica que todas esas rutas comparten las mismas condiciones de acceso.
También puedes crear un grupo reutilizable de middleware
Si la combinación se utiliza en varias partes, puedes crear un grupo.
<?php
use App\Http\Middleware\EnsureAccountIsActive;
use App\Http\Middleware\EnsureCompanyIsSelected;
use Illuminate\Foundation\Configuration\Middleware;
->withMiddleware(
function (Middleware $middleware): void {
$middleware->appendToGroup(
'account',
[
EnsureAccountIsActive::class,
EnsureCompanyIsSelected::class
]
);
}
)
Después:
Route::middleware([
'auth',
'account'
])->group(function () {
// Rutas privadas.
});
Esto puede resultar útil cuando varias capas representan una preocupación transversal común.
Los grupos web y api tienen responsabilidades diferentes
Las rutas web necesitan normalmente infraestructura relacionada con:
Cookies.
Sesiones.
Errores compartidos con vistas.
Protección de formularios.
Route model binding.
Las rutas de una API pueden tener necesidades distintas.
Laravel administra grupos predeterminados para esos contextos.
Cookies, sesiones y protección de requests dependen del pipeline.
El middleware cobra especial importancia en APIs autenticadas
auth:sanctum, autorización y límites de solicitudes.
Un middleware global participa en todas las solicitudes HTTP
Eso lo convierte en una herramienta poderosa, pero también en una decisión que deberías tomar con cuidado.
Puedes añadir un middleware al stack global desde:
<?php
use App\Http\Middleware\AddRequestId;
use Illuminate\Foundation\Configuration\Middleware;
->withMiddleware(
function (Middleware $middleware): void {
$middleware->append(
AddRequestId::class
);
}
)
¿Cuándo tiene sentido?
Cuando la preocupación verdaderamente afecta a prácticamente todos los requests.
¿Cuándo no?
Cuando una condición solo pertenece a tres rutas concretas.
Un middleware puede ejecutar lógica tanto antes como después del controller
Antes
<?php
public function handle(
Request $request,
Closure $next
): Response {
// Trabajo antes.
return $next($request);
}
Después
<?php
public function handle(
Request $request,
Closure $next
): Response {
$response = $next($request);
// Trabajo después.
return $response;
}
Observa la diferencia:
Middleware
↓
trabajo antes
↓
$next($request)
↓
Controller
↓
Response
↓
trabajo después
↓
Cliente
Esta estructura permite implementar preocupaciones relacionadas con entrada y salida del ciclo HTTP.
Un middleware también puede modificar la respuesta
Por ejemplo, puedes añadir un header relacionado con trazabilidad.
<?php
public function handle(
Request $request,
Closure $next
): Response {
$response = $next($request);
$response->headers->set(
'X-App-Version',
'1'
);
return $response;
}
Nuevamente, la cuestión importante es si esa tarea realmente pertenece a una preocupación transversal del ciclo HTTP.
Un middleware puede recibir parámetros desde la ruta
Imagina una condición relacionada con un rol.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
final class EnsureUserHasRole
{
public function handle(
Request $request,
Closure $next,
string $role
): Response {
$user = $request->user();
if (
$user === null
||
$user->role !== $role
) {
abort(403);
}
return $next($request);
}
}
Registramos un alias:
$middleware->alias([
'role' => EnsureUserHasRole::class
]);
Y la ruta puede indicar el parámetro.
Route::get(
'/administracion',
[AdminController::class, 'index']
)->middleware([
'auth',
'role:admin'
]);
También pueden existir múltiples parámetros
middleware:valor1,valor2
throttle es otro buen ejemplo de una preocupación transversal
Un endpoint no debería necesitar implementar manualmente lógica repetida para contar solicitudes.
Puedes aplicar límites mediante middleware según la estrategia de la aplicación.
Route::post(
'/contacto',
[ContactoController::class, 'store']
)->middleware(
'throttle:10,1'
);
Conceptualmente, una regla como esta limita el volumen aceptado dentro de una ventana determinada.
No todas las rutas deberían tener el mismo límite
Generar IA, intentar login, leer un catálogo y crear un pago tienen perfiles de riesgo y costo completamente diferentes.
auth:sanctum es middleware, pero Sanctum hace mucho más que una sola comprobación
En una API protegida puedes utilizar:
Route::get(
'/usuario',
function (Request $request) {
return $request->user();
}
)->middleware(
'auth:sanctum'
);
El middleware determina si existe una identidad aceptada por ese guard.
Después siguen existiendo otras preguntas:
¿Puede leer este recurso?
¿Pertenece a esta empresa?
¿Puede modificar este registro?
¿Tiene la capacidad necesaria?
El orden del middleware puede cambiar el resultado
Imagina dos capas:
auth
↓
account.active
El segundo middleware presupone que puede trabajar con un usuario.
Ejecutarlo antes de establecer correctamente la autenticación podría cambiar su comportamiento.
En casos avanzados existe prioridad de middleware
Laravel permite controlar el orden relativo cuando realmente es necesario.
$middleware->priority([
FirstMiddleware::class,
SecondMiddleware::class
]);
Si dos capas están excesivamente acopladas, quizá exista un problema de diseño más profundo.
Puedes excluir middleware de rutas concretas dentro de determinados grupos
Imagina un grupo donde casi todas las páginas necesitan una condición, excepto una.
Route::middleware([
EnsureAccountIsActive::class
])->group(function () {
Route::get(
'/panel',
[PanelController::class, 'index']
);
Route::get(
'/cuenta-bloqueada',
[AccountController::class, 'blocked']
)->withoutMiddleware([
EnsureAccountIsActive::class
]);
});
Tiene sentido:
un usuario con cuenta bloqueada necesita poder ver precisamente la página que explica por qué está bloqueada.
¿Cómo decidir si una regla pertenece a middleware?
Hazte una pregunta:
¿Esta regla existe porque estamos procesando una solicitud HTTP o porque el negocio necesita realizar una operación concreta?
Buen candidato a middleware
¿Está autenticado?
¿Está verificado?
¿Está activa la cuenta?
¿Excedió el límite de requests?
¿Puede entrar a esta zona?
Mal candidato
Calcular total del pedido.
Crear una factura.
Aplicar descuentos.
Reservar inventario.
Procesar un pago.
Esas últimas operaciones representan casos de uso del negocio.
Meterlas dentro de middleware puede esconder el verdadero flujo de la aplicación.
Determinado trabajo puede ejecutarse después de enviar la respuesta
Laravel admite
middleware
con un método
terminate().
<?php
final class LogRequestMetrics
{
public function handle(
Request $request,
Closure $next
): Response {
return $next($request);
}
public function terminate(
Request $request,
Response $response
): void {
// Trabajo posterior
// a la respuesta.
}
}
Puede resultar útil para determinadas tareas posteriores al envío de la respuesta, dependiendo del servidor y del tipo de trabajo.
Para procesos pesados, reintentos, trabajos demorados o ejecución distribuida, una queue suele representar otro problema arquitectónico.
Cada middleware añade trabajo al request
Una comprobación sencilla puede tener un costo mínimo.
Pero imagina un middleware que en cada request:
Ejecuta cinco consultas.
Consulta una API externa.
Carga relaciones enormes.
Procesa archivos.
Si además es global, ese costo se replica en toda la aplicación.
Un middleware también puede recibir dependencias mediante el contenedor
No necesitas crear manualmente todas las clases que utiliza.
<?php
final class EnsureSubscriptionIsActive
{
public function __construct(
private SubscriptionService $subscriptions
) {}
public function handle(
Request $request,
Closure $next
): Response {
if (
! $this->subscriptions
->isActive(
$request->user()
)
) {
abort(403);
}
return $next($request);
}
}
Esto permite que la capa HTTP delegue una comprobación especializada.
Pero cuida la dirección de dependencias
Si el middleware comienza a coordinar toda la aplicación, probablemente haya dejado de ser solamente una capa de entrada.
POO e inyección de dependencias →Prueba el comportamiento observable, no solamente la clase middleware
Si una ruta exige una cuenta activa, una prueba importante es comprobar que una cuenta inactiva no puede acceder.
<?php
public function test_usuario_inactivo_no_puede_entrar_al_panel(): void
{
$user = User::factory()
->create([
'activo' => false
]);
$this
->actingAs($user)
->get('/panel')
->assertForbidden();
}
Y también deberías comprobar el camino permitido.
<?php
public function test_usuario_activo_puede_entrar_al_panel(): void
{
$user = User::factory()
->create([
'activo' => true
]);
$this
->actingAs($user)
->get('/panel')
->assertOk();
}
Esto verifica el comportamiento que realmente importa al consumidor de la aplicación.
¿Middleware, Policy, Form Request, controller o servicio?
| Necesidad | Lugar habitual |
|---|---|
| Exigir autenticación | Middleware |
| Rate limiting | Middleware |
| Validar datos del request | Form Request |
| Autorizar modificación de un model | Policy |
| Coordinar HTTP | Controller |
| Ejecutar proceso de negocio | Servicio / caso de uso |
| Consultar persistencia | Eloquent / capa de datos |
Estas fronteras no son leyes universales, pero ayudan a evitar que cada clase termine haciendo de todo.
Los errores más comunes al usar middleware en Laravel
En qué orden aprender middleware en Laravel
HTTP
Request y response.
Pipeline
Comprende $next().
auth
Observa un middleware real.
Personalizado
Crea una condición propia.
Aliases
Simplifica rutas.
Groups
Reutiliza conjuntos.
Parameters
Configura comportamiento.
Before / after
Comprende el recorrido.
Prioridad
Estudia dependencias entre capas.
Testing
Prueba acceso y rechazo.
Middleware funciona mejor cuando protege el límite HTTP sin esconder el negocio
Utiliza middleware para preocupaciones transversales de requests y responses.
Comprende qué hace $next($request) antes de memorizar sintaxis.
Aplica middleware solamente a las rutas que realmente lo necesitan.
Utiliza aliases cuando mejoren la claridad.
Agrupa middleware cuando varias rutas comparten condiciones.
Aprende los grupos web y api.
Considera el orden cuando una capa depende de otra.
No conviertas middleware en un servicio de negocio disfrazado.
Utiliza Policies para autorización específica sobre recursos.
Prueba tanto los accesos permitidos como los denegados.
Un buen middleware hace visible una condición transversal del request; un mal middleware esconde procesos importantes del negocio en un lugar donde nadie espera encontrarlos.
Continúa profundizando en Laravel
Preguntas sobre middleware en Laravel
Es una capa del ciclo HTTP que puede inspeccionar, modificar, permitir o detener una solicitud antes de que continúe hacia otras capas de la aplicación.
Puedes generarlo con el comando php artisan make:middleware. La clase creada contiene un método handle que recibe el Request, el callback $next y opcionalmente parámetros adicionales.
Entrega la solicitud a la siguiente capa del pipeline. Si el middleware devuelve otra respuesta antes de ejecutar $next, la solicitud puede detenerse en ese punto.
Puedes asignar directamente la clase a una ruta. Cuando necesitas aliases, middleware globales, grupos o configuraciones relacionadas, Laravel permite gestionarlos desde bootstrap/app.php.
Middleware resulta adecuado para preocupaciones transversales del request, como exigir autenticación. Una Policy organiza reglas de autorización relacionadas con acciones sobre un model o recurso concreto.
auth es un alias del middleware de autenticación incluido en Laravel. Se utiliza para exigir una identidad autenticada antes de permitir que una ruta protegida continúe.
Sí. Los parámetros adicionales se entregan al método handle después de $next. Esto permite reutilizar el mismo middleware con valores distintos según la ruta.
Es un middleware incorporado al stack global de la aplicación, por lo que participa en todas las solicitudes HTTP. Solo debería utilizarse cuando la preocupación realmente sea global.
Sí. Un middleware puede obtener primero la respuesta mediante $next($request), ejecutar lógica después y finalmente devolver esa respuesta. Laravel también admite middleware terminables para determinados trabajos posteriores.
Middleware deja de parecer magia cuando entiendes por dónde viaja una solicitud
Aprende primero request y response, después $next(), middleware de ruta, aliases, grupos y parámetros. Finalmente profundiza en autenticación, autorización, APIs y testing. Así sabrás no solamente cómo crear middleware, sino cuándo realmente corresponde utilizarlo.
Volver al hub Laravel →