¿Qué es un middleware?
Los middlewares son funciones que actúan como intermediarios en el procesamiento de peticiones HTTP en Express. Estas funciones se ejecutan secuencialmente entre el momento en que el servidor recibe una petición y antes de enviar la respuesta al cliente.
Imagina los middlewares como filtros o capas de procesamiento que se aplican a cada petición. Cada middleware puede realizar tareas específicas como validar datos, autenticar usuarios, registrar información o modificar los objetos de petición y respuesta antes de pasar el control al siguiente middleware en la cadena.
Anatomía de un middleware
Un middleware en Express es una función que recibe tres parámetros principales:
function miMiddleware(req, res, next) {
// Lógica del middleware
console.log('Procesando petición...');
// Pasar control al siguiente middleware
next();
}
Los parámetros que recibe todo middleware son:
**req**
(request): El objeto de petición que contiene información sobre la solicitud HTTP**res**
(response): El objeto de respuesta que permite enviar datos al cliente**next**
: Una función que debe llamarse para pasar el control al siguiente middleware
Tipos de middlewares según su alcance
Express permite aplicar middlewares de diferentes formas según el alcance que necesitemos:
Middleware global para toda la aplicación:
const express = require('express');
const app = express();
// Se ejecuta en TODAS las rutas
app.use((req, res, next) => {
console.log(`${req.method} ${req.path} - ${new Date().toISOString()}`);
next();
});
app.get('/', (req, res) => {
res.send('Página principal');
});
Middleware específico para una ruta:
// Solo se ejecuta en esta ruta específica
app.get('/admin',
(req, res, next) => {
console.log('Verificando acceso a admin...');
next();
},
(req, res) => {
res.send('Panel de administración');
}
);
Middleware para un grupo de rutas con un prefijo:
// Se ejecuta solo en rutas que empiecen por /api
app.use('/api', (req, res, next) => {
console.log('Procesando petición de API');
next();
});
Funciones esenciales de los middlewares
Los middlewares pueden cumplir múltiples propósitos en una aplicación web:
- Logging: Registrar información sobre las peticiones entrantes
- Autenticación: Verificar si el usuario tiene permisos para acceder
- Validación: Comprobar que los datos recibidos son correctos
- Parsing: Procesar el cuerpo de las peticiones (JSON, formularios, etc.)
- Manejo de errores: Capturar y gestionar errores de forma centralizada
- CORS: Gestionar las políticas de acceso entre dominios
Ejemplo práctico básico
Aquí tienes un ejemplo que muestra cómo varios middlewares trabajan juntos:
const express = require('express');
const app = express();
// Middleware 1: Logger
app.use((req, res, next) => {
console.log(`📝 ${req.method} ${req.url}`);
next();
});
// Middleware 2: Verificar hora de acceso
app.use((req, res, next) => {
const hour = new Date().getHours();
if (hour < 8 || hour > 22) {
return res.status(403).send('⏰ Servicio no disponible fuera de horario');
}
next();
});
// Middleware 3: Agregar información al objeto request
app.use((req, res, next) => {
req.timestamp = new Date().toISOString();
next();
});
// Ruta final
app.get('/', (req, res) => {
res.json({
message: 'Hola mundo',
processedAt: req.timestamp
});
});
app.listen(3000, () => {
console.log('Servidor corriendo en puerto 3000');
});
En este ejemplo, cada petición pasa por tres middlewares antes de llegar a la ruta final. El primer middleware registra la petición, el segundo verifica el horario de acceso, y el tercero añade información temporal que puede ser utilizada por las rutas.
Control de flujo con next()
La función **next()**
es fundamental para el funcionamiento de los middlewares. Si no la llamamos, la petición se quedará "colgada" y nunca llegará a su destino:
// ❌ Middleware problemático - no llama next()
app.use((req, res, next) => {
console.log('Este middleware no continúa...');
// Sin next(), la petición se queda aquí
});
// ✅ Middleware correcto
app.use((req, res, next) => {
console.log('Este middleware continúa correctamente');
next(); // Pasa el control al siguiente middleware
});
También podemos interrumpir el flujo llamando a next()
con un error o enviando una respuesta directamente:
app.use((req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).send('Token requerido');
}
next(); // Solo continúa si hay autorización
});
express.json() y middlewares propios
El middleware express.json()
El middleware **express.json()**
es uno de los middlewares más utilizados en aplicaciones Express modernas. Su función principal es parsear automáticamente el cuerpo de las peticiones HTTP que contienen datos en formato JSON, convirtiéndolos en un objeto JavaScript accesible desde req.body
.
Sin este middleware, cuando un cliente envía datos JSON al servidor, estos llegan como un stream de bytes que debemos procesar manualmente. express.json()
se encarga de esta tarea automáticamente:
const express = require('express');
const app = express();
// Habilitar el parsing de JSON
app.use(express.json());
app.post('/usuarios', (req, res) => {
// Sin express.json(), req.body sería undefined
console.log(req.body); // { nombre: "Juan", edad: 25 }
res.json({
mensaje: 'Usuario recibido',
datos: req.body
});
});
Configuración y opciones de express.json()
Este middleware acepta varias opciones de configuración que permiten personalizar su comportamiento:
app.use(express.json({
limit: '10mb', // Tamaño máximo del cuerpo de la petición
strict: true, // Solo acepta arrays y objetos válidos
type: 'application/json' // Tipos MIME que debe procesar
}));
Las opciones más importantes son:
**limit**
: Define el tamaño máximo permitido para el cuerpo de la petición**strict**
: Cuando estrue
, solo acepta objetos y arrays JSON válidos**type**
: Especifica qué tipos de contenido debe procesar el middleware
Ejemplo práctico con validación
Aquí tienes un ejemplo que muestra cómo usar express.json()
junto con validación básica:
const express = require('express');
const app = express();
// Configurar express.json() con límite personalizado
app.use(express.json({ limit: '1mb' }));
app.post('/productos', (req, res) => {
const { nombre, precio, categoria } = req.body;
// Validar que los datos necesarios estén presentes
if (!nombre || !precio || !categoria) {
return res.status(400).json({
error: 'Faltan campos obligatorios: nombre, precio, categoria'
});
}
// Simular guardado del producto
const producto = {
id: Date.now(),
nombre,
precio: parseFloat(precio),
categoria
};
res.status(201).json({
mensaje: 'Producto creado exitosamente',
producto
});
});
Creando middlewares propios
Los middlewares personalizados nos permiten implementar lógica específica para nuestras aplicaciones. La estructura básica siempre sigue el mismo patrón:
// Estructura básica de un middleware personalizado
function miMiddleware(req, res, next) {
// Lógica personalizada
// Modificar req o res si es necesario
// Llamar next() para continuar
next();
}
// Aplicar el middleware
app.use(miMiddleware);
Middleware de autenticación personalizado
Un caso común es crear un middleware para verificar tokens de autenticación:
function verificarToken(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({
error: 'Token de autorización requerido'
});
}
// Simular verificación de token
if (token !== 'Bearer token-secreto') {
return res.status(403).json({
error: 'Token inválido'
});
}
// Añadir información del usuario al objeto request
req.usuario = {
id: 1,
nombre: 'Juan Pérez',
rol: 'admin'
};
next();
}
// Aplicar middleware solo a rutas protegidas
app.get('/admin', verificarToken, (req, res) => {
res.json({
mensaje: `Bienvenido ${req.usuario.nombre}`,
panel: 'Administración'
});
});
Middleware de validación de datos
Otro ejemplo útil es un middleware para validar el formato de datos antes de procesarlos:
function validarEmail(req, res, next) {
const { email } = req.body;
if (!email) {
return res.status(400).json({
error: 'El campo email es obligatorio'
});
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return res.status(400).json({
error: 'El formato del email no es válido'
});
}
next();
}
app.post('/registro', validarEmail, (req, res) => {
// En este punto sabemos que el email es válido
res.json({
mensaje: 'Usuario registrado correctamente',
email: req.body.email
});
});
Middlewares con parámetros
Podemos crear middlewares configurables que reciben parámetros y devuelven la función middleware:
// Factory function que retorna un middleware
function limitarIntentos(maxIntentos) {
const intentos = new Map();
return (req, res, next) => {
const ip = req.ip;
const contador = intentos.get(ip) || 0;
if (contador >= maxIntentos) {
return res.status(429).json({
error: 'Demasiados intentos, intenta más tarde'
});
}
intentos.set(ip, contador + 1);
// Limpiar contador después de 1 minuto
setTimeout(() => {
intentos.delete(ip);
}, 60000);
next();
};
}
// Usar el middleware con configuración específica
app.post('/login', limitarIntentos(3), (req, res) => {
// Lógica de login
res.json({ mensaje: 'Intento de login procesado' });
});
Middleware de logging personalizado
Un middleware muy útil es uno que registre información detallada sobre las peticiones:
function loggerPersonalizado(req, res, next) {
const inicio = Date.now();
// Interceptar el método res.json para medir tiempo de respuesta
const originalJson = res.json;
res.json = function(...args) {
const duracion = Date.now() - inicio;
console.log(`📊 ${req.method} ${req.url} - ${res.statusCode} - ${duracion}ms`);
return originalJson.apply(this, args);
};
console.log(`🔍 ${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
}
app.use(loggerPersonalizado);
Combinando múltiples middlewares
Los middlewares propios pueden combinarse para crear funcionalidades más complejas:
const express = require('express');
const app = express();
// Middleware base para parsing JSON
app.use(express.json());
// Middleware personalizado para logging
app.use(loggerPersonalizado);
// Ruta con múltiples middlewares personalizados
app.post('/api/usuarios',
validarEmail,
verificarToken,
(req, res) => {
res.json({
mensaje: 'Usuario creado',
usuario: req.body,
createdBy: req.usuario.nombre
});
}
);
Flujo de ejecución
El flujo de ejecución en los middlewares de Express sigue un patrón secuencial y predecible que es fundamental comprender para desarrollar aplicaciones robustas. Cada petición HTTP pasa a través de una cadena de middlewares en un orden específico, donde cada uno puede modificar, validar o interceptar la petición antes de que llegue a su destino final.
La cadena de middlewares
Cuando una petición llega al servidor Express, se crea una cola de ejecución basada en el orden en que se registraron los middlewares. Este orden es crítico y determina cómo se procesará cada petición:
const express = require('express');
const app = express();
// Middleware 1 - Se ejecuta primero
app.use((req, res, next) => {
console.log('1. Primer middleware');
req.paso = 1;
next();
});
// Middleware 2 - Se ejecuta segundo
app.use((req, res, next) => {
console.log('2. Segundo middleware');
req.paso = 2;
next();
});
// Middleware 3 - Se ejecuta tercero
app.use('/usuarios', (req, res, next) => {
console.log('3. Middleware específico para /usuarios');
req.paso = 3;
next();
});
// Ruta final
app.get('/usuarios', (req, res) => {
console.log('4. Manejador de ruta final');
res.json({ mensaje: 'Usuario encontrado', pasos: req.paso });
});
En este ejemplo, una petición GET /usuarios
seguirá el flujo: Middleware 1 → Middleware 2 → Middleware 3 → Ruta final.
Escenarios de flujo de ejecución
El comportamiento del flujo puede variar según las acciones que tome cada middleware:
Flujo normal con next():
app.use((req, res, next) => {
console.log('✅ Procesando...');
// Modifica la petición y continúa
req.timestamp = Date.now();
next(); // Continúa al siguiente middleware
});
app.use((req, res, next) => {
console.log('✅ Validando...');
// Continúa normalmente
next();
});
app.get('/', (req, res) => {
res.json({ mensaje: 'Completado', tiempo: req.timestamp });
});
Flujo interrumpido con respuesta directa:
app.use((req, res, next) => {
console.log('🔍 Verificando permisos...');
if (!req.headers.authorization) {
// Interrumpe el flujo enviando respuesta
return res.status(401).json({ error: 'No autorizado' });
}
next(); // Solo continúa si hay autorización
});
app.use((req, res, next) => {
console.log('⚠️ Este middleware NO se ejecuta si falta autorización');
next();
});
Flujo con manejo de errores:
app.use((req, res, next) => {
console.log('🔄 Procesando datos...');
try {
// Simular procesamiento que puede fallar
if (Math.random() < 0.3) {
throw new Error('Error simulado');
}
next();
} catch (error) {
// Pasar error al siguiente middleware de manejo de errores
next(error);
}
});
app.use((req, res, next) => {
console.log('✨ Este middleware solo se ejecuta si no hay errores');
next();
});
// Middleware de manejo de errores (4 parámetros)
app.use((err, req, res, next) => {
console.log('❌ Manejando error:', err.message);
res.status(500).json({ error: err.message });
});
Visualización del flujo de ejecución
Para entender mejor el flujo, podemos crear un middleware de debugging que trace la ejecución:
function crearDebugger(nombre) {
return (req, res, next) => {
console.log(`🔹 ENTRANDO: ${nombre} | ${req.method} ${req.url}`);
// Interceptar next() para saber cuándo sale del middleware
const originalNext = next;
const debugNext = (error) => {
if (error) {
console.log(`🔸 SALIENDO CON ERROR: ${nombre} | Error: ${error.message}`);
} else {
console.log(`🔸 SALIENDO: ${nombre} | Continuando flujo`);
}
originalNext(error);
};
// Interceptar respuesta para detectar si termina aquí
const originalSend = res.send;
res.send = function(...args) {
console.log(`🔹 RESPUESTA ENVIADA DESDE: ${nombre}`);
return originalSend.apply(this, args);
};
next = debugNext;
next();
};
}
// Aplicar debugging a varios middlewares
app.use(crearDebugger('Autenticación'));
app.use(crearDebugger('Validación'));
app.use(crearDebugger('Logging'));
Orden de ejecución con diferentes tipos de middlewares
El orden de registro determina la prioridad de ejecución, pero hay matices importantes:
// 1. Middlewares globales (se ejecutan primero)
app.use(express.json());
app.use((req, res, next) => {
console.log('Global middleware');
next();
});
// 2. Middlewares con rutas específicas
app.use('/api', (req, res, next) => {
console.log('Solo para rutas /api/*');
next();
});
// 3. Middlewares de rutas específicas (más específicos)
app.get('/api/usuarios/:id',
(req, res, next) => {
console.log('Middleware específico para GET /api/usuarios/:id');
next();
},
(req, res) => {
res.json({ usuario: req.params.id });
}
);
// 4. Middleware comodín (se ejecuta si no coincide ninguna ruta anterior)
app.use('*', (req, res) => {
res.status(404).json({ error: 'Ruta no encontrada' });
});
Flujo asíncrono en middlewares
Los middlewares pueden manejar operaciones asíncronas, pero requieren un manejo cuidadoso del flujo:
// Middleware asíncrono con async/await
app.use(async (req, res, next) => {
try {
console.log('🔄 Iniciando operación asíncrona...');
// Simular consulta a base de datos
await new Promise(resolve => setTimeout(resolve, 100));
req.datosUsuario = { id: 123, nombre: 'Juan' };
console.log('✅ Operación asíncrona completada');
next(); // Continúa después de completar la operación
} catch (error) {
console.log('❌ Error en operación asíncrona');
next(error); // Pasa el error al manejador de errores
}
});
// Middleware con promesas manuales
app.use((req, res, next) => {
console.log('🔄 Validando token...');
validarTokenAsync(req.headers.authorization)
.then(usuario => {
req.usuario = usuario;
next(); // Continúa si la validación es exitosa
})
.catch(error => {
next(error); // Pasa el error si falla la validación
});
});
function validarTokenAsync(token) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (token === 'token-valido') {
resolve({ id: 1, nombre: 'Usuario' });
} else {
reject(new Error('Token inválido'));
}
}, 50);
});
}
Debugging y troubleshooting del flujo
Cuando el flujo no se comporta como esperamos, podemos usar técnicas de debugging:
// Middleware para monitorear el stack de middlewares
app.use((req, res, next) => {
const originalNext = next;
next = (error) => {
if (error) {
console.log('🚨 Error detectado en el flujo:', error.message);
console.trace('Stack trace del error');
}
return originalNext(error);
};
console.log(`📍 Middleware ejecutado: ${req.method} ${req.originalUrl}`);
next();
});
// Middleware para detectar middlewares "colgados"
function timeoutMiddleware(tiempoLimite = 5000) {
return (req, res, next) => {
const timeout = setTimeout(() => {
console.log('⏰ ADVERTENCIA: Middleware tardando demasiado');
console.log(`Ruta: ${req.method} ${req.originalUrl}`);
}, tiempoLimite);
const originalNext = next;
next = (...args) => {
clearTimeout(timeout);
return originalNext(...args);
};
next();
};
}
app.use(timeoutMiddleware(3000)); // Advertir si tarda más de 3 segundos
Ejemplo completo de flujo complejo
Aquí tienes un ejemplo que demuestra un flujo de ejecución complejo con múltiples escenarios:
const express = require('express');
const app = express();
// 1. Middleware de logging (siempre se ejecuta primero)
app.use((req, res, next) => {
req.inicioTiempo = Date.now();
console.log(`🔵 INICIO: ${req.method} ${req.url}`);
next();
});
// 2. Parsing de JSON
app.use(express.json());
// 3. Middleware de autenticación condicional
app.use('/protected', (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
console.log('🔴 FLUJO INTERRUMPIDO: Sin token');
return res.status(401).json({ error: 'Token requerido' });
}
console.log('🟢 FLUJO CONTINÚA: Token presente');
req.usuario = { id: 1, nombre: 'Usuario Autenticado' };
next();
});
// 4. Middleware de validación de datos (solo para POST)
app.use((req, res, next) => {
if (req.method === 'POST' && req.body) {
if (!req.body.nombre) {
console.log('🔴 FLUJO INTERRUMPIDO: Validación fallida');
return res.status(400).json({ error: 'Nombre requerido' });
}
}
console.log('🟢 FLUJO CONTINÚA: Validación exitosa');
next();
});
// 5. Rutas finales
app.get('/public', (req, res) => {
console.log('🎯 DESTINO ALCANZADO: Ruta pública');
res.json({ mensaje: 'Acceso público exitoso' });
});
app.get('/protected/data', (req, res) => {
console.log('🎯 DESTINO ALCANZADO: Ruta protegida');
res.json({
mensaje: 'Datos protegidos',
usuario: req.usuario.nombre
});
});
app.post('/protected/create', (req, res) => {
console.log('🎯 DESTINO ALCANZADO: Creación con validación');
res.json({
mensaje: 'Recurso creado',
datos: req.body,
usuario: req.usuario.nombre
});
});
// 6. Middleware de finalización (mide tiempo total)
app.use((req, res, next) => {
const tiempoTotal = Date.now() - req.inicioTiempo;
console.log(`⚪ FIN: ${req.method} ${req.url} - ${tiempoTotal}ms`);
});
app.listen(3000);
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en Node
Documentación oficial de Node
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Node es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Node
Explora más contenido relacionado con Node y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender qué es un middleware y su función en Express.
- Aprender a crear y aplicar middlewares globales, específicos y con parámetros.
- Utilizar el middleware express.json() para parsear cuerpos JSON.
- Gestionar el flujo de ejecución y manejo de errores en middlewares.
- Implementar middlewares personalizados para autenticación, validación y logging.