Middleware: Interceptores HTTP
Cómo implementar middleware de request/response para logging, auth y manejo de errores en Python, JavaScript y Java.
Visión general
Cada request a tu API necesita el mismo trabajo previo: loguearlo, verificar el token de auth, añadir headers CORS, medir cuánto tarda, capturar lo que explote. El middleware es la capa que hace ese trabajo — funciones que se ubican entre el request entrante y tu route handler, ejecutándose en un pipeline antes (y a veces después) del handler.
Cada middleware de la cadena puede inspeccionar o modificar el request, abortarlo temprano (un 401 de un chequeo de auth), o pasar el control a la siguiente capa con next(). El orden en que los registras es el orden en que se ejecutan — por eso el bug clásico es poner el error handler antes de las rutas.
Cuándo usarlo
Usa esta recipe cuando:
- Aplicar autenticación antes de que los route handlers se ejecuten. Consulta JWT Authentication para patrones de auth.
- Loguear todos los requests entrantes y tiempos de respuesta. Consulta Logging para logging estructurado.
- Añadir headers CORS o de seguridad a cada response. Consulta Manejo de CORS para la configuración de CORS.
- Validar cuerpos de request o parámetros de query en un solo lugar en vez de dentro de cada handler.
- Implementar rate limiting o throttling de requests. Consulta Rate Limiting con Redis para rate limiting basado en Redis.
Esta recipe muestra la forma del middleware agnóstica a framework — las cadenas de Express y FastAPI de abajo son ejecutables en el repositorio companion. Para composiciones específicas de Express — wrappers async, scoping por path de montaje, error boundaries — consulta patrones de middleware en Express.
Solución
Python (FastAPI)
from fastapi import Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
import time
# Middleware personalizado
@app.middleware("http")
async def log_requests(request: Request, call_next):
start = time.time()
response = await call_next(request)
duration = time.time() - start
print(f"{request.method} {request.url.path} - {response.status_code} ({duration:.3f}s)")
return response
# Middleware CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["https://example.com"],
allow_methods=["GET", "POST"],
)
# Dependencia de auth (usada como middleware en rutas)
async def verify_token(request: Request):
token = request.headers.get("Authorization")
if not token or not token.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing token")
return token
@app.get("/protected", dependencies=[Depends(verify_token)])
async def protected():
return {"message": "Secret data"}
JavaScript (Express)
const express = require('express');
const app = express();
// Middleware de logging
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.path} - ${res.statusCode} (${duration}ms)`);
});
next();
});
// Middleware de auth
function authMiddleware(req, res, next) {
const token = req.headers.authorization;
if (!token || !token.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Unauthorized' });
}
req.user = verifyToken(token); // tu lógica de verificación de token
next();
}
app.use('/api/protected', authMiddleware);
// Middleware de manejo de errores (debe ir al final)
app.use((err, req, res, next) => {
console.error(err);
res.status(err.status || 500).json({ error: err.message || 'Internal Server Error' });
});
Java (Spring Boot)
@Component
public class LoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
req.setAttribute("startTime", System.currentTimeMillis());
return true;
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) {
long start = (Long) req.getAttribute("startTime");
long duration = System.currentTimeMillis() - start;
System.out.println(req.getMethod() + " " + req.getRequestURI() + " - " + res.getStatus() + " (" + duration + "ms)");
}
}
// Registrar interceptor
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoggingInterceptor loggingInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor).addPathPatterns("/api/**");
}
}
Explicación
El pipeline es la idea central. Un request entra por la capa más externa y viaja hacia adentro a través de cada middleware hasta llegar al handler; el response luego vuelve hacia afuera por las mismas capas en orden inverso:
Cuando auth rechaza el request, next() nunca se llama y el handler nunca se ejecuta — ese comportamiento de “fail fast” es lo que hace del middleware el hogar correcto para auth, validación y rate limiting.
Cada framework expone hooks en distintos puntos del pipeline:
- Antes del handler (Express
app.use, FastAPIcall_next, SpringpreHandle): inspeccionar, modificar o bloquear el request. - De camino a la salida (Express
res.on('finish'), SpringpostHandle): añadir headers, loguear, registrar métricas. - Después de enviar el response (Spring
afterCompletion): limpieza, logging asíncrono. - Middleware de errores (la firma de cuatro argumentos
(err, req, res, next)de Express): captura lo que se lance en cualquier capa posterior. Express lo identifica solo por el número de argumentos — una función de tres nunca se llama para errores. Debe registrarse en último lugar.
Middleware vs decoradores y guards: el middleware corre para todo request que coincida con su path de montaje; un decorador o dependencia (Depends de FastAPI, un guard de NestJS, @PreAuthorize de Spring) se adhiere a una ruta concreta. Usa middleware cuando el problema es verdaderamente transversal — logging, CORS, correlation IDs — y para reglas por endpoint usa una dependencia o un guard. Un chequeo de auth por ruta hecho como middleware global te obliga a mantener una lista de excepciones, y eso es lo que todo el mundo olvida.
Un ejemplo concreto de por qué importa el orden: los navegadores envían un preflight OPTIONS antes de cualquier request cross-origin — y los preflights no llevan credenciales. Registra auth antes de CORS y cada preflight muere en un 401 antes de que se adjunten los headers CORS; el navegador reporta un “error de CORS” que no es realmente un problema de CORS. Pon CORS primero y el preflight obtiene sus headers y hace short-circuit, mientras que los requests reales siguen pasando por auth. La mayoría de bugs “misteriosos” de middleware vienen de una asunción de orden como esta.
Lo que funciona
- Registra en el orden en que debe fluir el request: logging y correlation IDs primero (para que todo lo posterior quede trazado), luego CORS y headers de seguridad, luego auth, luego las rutas — el error handler en último lugar.
- Fall fast: un middleware de auth que devuelve 401 evita que el handler se ejecute. Rechazar barato siempre gana a procesar caro.
- No te tragues errores: en Express, envuelve el middleware async en try/catch y reenvía con
next(err)— un rechazo async sin manejar salta completamente el error handler. - Sé stateless: datos de scope-request en variables a nivel de módulo se mezclan entre requests concurrentes. Adjunta a
req(Express),request.state(FastAPI) o atributos del request (Spring). - Usa los middlewares del framework para lo aburrido:
helmetymorganen Express,CORSMiddlewareyGZipMiddlewareen FastAPI, los hooks deWebMvcConfigureren Spring están todos probados — escribe middleware propio solo donde ellos se quedan cortos. - Inyecta, no construyas: en Spring y FastAPI, inyecta servicios en el interceptor en lugar de instanciarlos dentro — más barato y testeable.
Errores comunes
- Olvidar
next(): el request se queda colgado para siempre — sin response, sin error, solo un timeout. Sigue siendo el bug de middleware de Express más común con diferencia. - Error handler no está al final: si registras el middleware de error de cuatro argumentos antes de las rutas, simplemente nunca se dispara.
- Mutar
reqcon datos no confiables: asignarreq.userdesde query params o headers sin validar sobrescribe estado confiable con input del atacante. - Trabajo síncrono pesado en la cadena: el middleware corre en cada request — un pico de CPU bloqueante frena todos los demás requests del proceso.
- Llamar
next()dos veces: normalmente por una rama que olvida hacerreturntras llamarlo una vez. En Express viejo crashea el proceso; en versiones nuevas produce responses corruptos.
Solución de Problemas
- Los requests se cuelgan en Express: algún middleware aguas arriba no llama a
next(). Bisecciona comentando líneasapp.use, o añade unconsole.logde rastro por capa y mira dónde se corta. - Errores de CORS en el navegador: el preflight
OPTIONStambién necesita los headers — registra el middleware CORS antes del auth, o el preflight muere en un 401. - Auth corre pero la ruta nunca: te falta
next()tras la verificación exitosa. - Middleware silencioso en algunas rutas: revisa el path de montaje —
app.use('/api', mw)salta todo lo que esté fuera de/api. - El handler lanza error pero recibes una página de error HTML: el error nunca llegó a tu middleware — le falta el primer parámetro
err, y las funciones de tres argumentos se omiten para errores.
Lecturas Recomendadas
- Documentación de middleware de Express —
app.use, paths de montaje y semántica del middleware de errores. - Middleware de FastAPI — middleware basado en
call_nexty la lista de middlewares integrados. - Spring HandlerInterceptor — ciclo de vida preHandle/postHandle/afterCompletion.
- Companion ejecutable — cadenas de middleware en Express y FastAPI listas para correr.
Preguntas frecuentes
¿Qué hace exactamente next() en middleware de Express?
Pasa el control a la siguiente función de la cadena — el siguiente middleware registrado o el route handler. Si lo omites, el request se queda colgado para siempre. Si lo llamas con un argumento (next(err)), Express salta directamente al middleware de errores, ignorando todo lo que hay en medio.
¿Puede el middleware modificar el body del response?
En Express, no de forma limpia — el body puede estar ya transmitido para cuando el middleware lo ve. Puedes envolver res.send/res.json, pero es delicado. FastAPI y Spring facilitan el post-procesamiento; en cualquier caso, los headers y status codes son lo seguro de tocar.
¿Cuántas capas de middleware son demasiadas?
No hay un límite fijo, pero cada capa añade latencia a cada request. Si tu cadena supera la docena de elementos, algunos probablemente pertenecen al nivel de ruta. Profilea antes de cortar — un middleware de logging cuesta microsegundos; una verificación de token síncrona en cada request es lo caro.
¿La validación va en el middleware o en el handler?
Ambas funcionan; lo que importa es dónde aplica la regla. El middleware es correcto cuando la misma validación cubre muchas rutas ("todo body POST menor de 1MB, JSON válido"). La validación por ruta — "el user_id debe existir" — pertenece a una dependencia, un guard o el propio handler.
¿Por qué no se llama a mi error handler de Express?
Dos sospechosos habituales: está registrado antes de las rutas (debe ir al final), o le falta el parámetro err — Express reconoce el middleware de errores solo por su firma de cuatro argumentos.
Recursos Relacionados
Llamar a una API REST: Python, JS, Java y Go
Cómo hacer peticiones HTTP a una API REST y manejar la respuesta JSON en Python, JavaScript, Java y Go.
RecipeManejar Errores en APIs con RFC 7807
Patrones para un manejo de errores de API consistente y predecible en varios lenguajes y frameworks.
RecipeAutenticación JWT
Firma, verifica y refresca JWTs en Python, JavaScript y Java — flujos access/refresh, pares de claves RS256 y revocación de tokens bien hecha.
RecipePatrones de Composicion de Middleware en Express.js
Construye aplicaciones Express mantenibles usando patrones de composicion de middleware para autenticacion, validacion, manejo de errores, propagacion de contexto y wrappers de rutas async
RecipeConfigurar CORS en Express, Flask y Spring Boot
Cómo configurar los encabezados de Cross-Origin Resource Sharing (CORS) correctamente para APIs, SPAs y funciones serverless sin abrir agujeros de seguridad.
RecipeImplementar Rate Limiting de APIs con Redis
Protege APIs del abuso con limitadores token bucket y ventana deslizante respaldados por Redis, con ráfagas controladas y coordinación distribuida