Express
Tutorial Express: Qué es un Middleware
Aprende qué es un middleware en Express, su función, tipos y cómo controla el flujo request-response en aplicaciones Node.js.
Aprende Express y certifícateConcepto de middleware
El middleware en Express es una función que se ejecuta durante el ciclo de vida de una petición HTTP, actuando como intermediario entre la recepción de la petición y el envío de la respuesta. Estas funciones tienen acceso a los objetos de petición (req
), respuesta (res
) y a la siguiente función middleware en la pila (next
).
Un middleware puede realizar múltiples tareas: modificar los objetos de petición y respuesta, ejecutar código personalizado, finalizar el ciclo request-response o pasar el control al siguiente middleware. Esta flexibilidad convierte al middleware en el mecanismo fundamental para estructurar aplicaciones Express de manera modular y reutilizable.
Anatomía de una función middleware
Una función middleware en Express sigue una estructura específica con tres parámetros principales:
function miMiddleware(req, res, next) {
// Lógica del middleware
console.log('Middleware ejecutado');
// Pasar control al siguiente middleware
next();
}
El parámetro req
representa la petición HTTP entrante y contiene información como headers, parámetros, body y query strings. El parámetro res
proporciona métodos para enviar la respuesta al cliente. El parámetro next
es una función que debe llamarse para transferir el control al siguiente middleware en la pila.
Tipos de middleware según su alcance
Express organiza el middleware en diferentes niveles de aplicación según su alcance y propósito:
Middleware a nivel de aplicación se registra directamente en la instancia de la aplicación:
const express = require('express');
const app = express();
// Middleware que se ejecuta en todas las rutas
app.use((req, res, next) => {
console.log(`${req.method} ${req.path} - ${new Date().toISOString()}`);
next();
});
// Middleware específico para rutas que empiecen con /api
app.use('/api', (req, res, next) => {
req.apiVersion = 'v1';
next();
});
Middleware a nivel de router se aplica únicamente a las rutas definidas en un router específico:
const router = express.Router();
// Solo afecta a las rutas de este router
router.use((req, res, next) => {
req.timestamp = Date.now();
next();
});
router.get('/users', (req, res) => {
res.json({ timestamp: req.timestamp });
});
app.use('/api', router);
Middleware integrado y de terceros
Express incluye middleware integrado para tareas comunes. El más utilizado es express.static
para servir archivos estáticos:
// Servir archivos desde el directorio 'public'
app.use(express.static('public'));
// Parsear JSON en el body de las peticiones
app.use(express.json());
// Parsear datos de formularios URL-encoded
app.use(express.urlencoded({ extended: true }));
El middleware de terceros amplía las capacidades de Express mediante paquetes npm especializados:
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
// Habilitar CORS
app.use(cors());
// Añadir headers de seguridad
app.use(helmet());
// Logging de peticiones HTTP
app.use(morgan('combined'));
Orden de ejecución y control de flujo
El orden de registro del middleware determina su secuencia de ejecución. Express procesa el middleware en el orden en que se define, creando una pila de funciones:
// Primer middleware - siempre se ejecuta primero
app.use((req, res, next) => {
console.log('Middleware 1');
next();
});
// Segundo middleware - se ejecuta después del primero
app.use((req, res, next) => {
console.log('Middleware 2');
next();
});
// Ruta final
app.get('/', (req, res) => {
console.log('Manejador de ruta');
res.send('Respuesta enviada');
});
Si un middleware no llama a next()
, la cadena de ejecución se detiene y la petición queda pendiente. Esto es útil para middleware que finaliza la respuesta:
app.use('/admin', (req, res, next) => {
if (!req.headers.authorization) {
// No llamamos next(), terminamos aquí
return res.status(401).json({ error: 'No autorizado' });
}
next(); // Solo continúa si hay autorización
});
Middleware de manejo de errores
Express distingue el middleware de errores por su signatura de cuatro parámetros. Este tipo de middleware se ejecuta cuando se produce un error o cuando se llama a next()
con un argumento:
// Middleware normal
app.use((req, res, next) => {
// Simular un error
const error = new Error('Algo salió mal');
next(error); // Pasar error al middleware de errores
});
// Middleware de manejo de errores (4 parámetros)
app.use((err, req, res, next) => {
console.error(err.message);
res.status(500).json({
error: 'Error interno del servidor',
message: err.message
});
});
El middleware de errores debe definirse después de todas las rutas y middleware normales para capturar errores de toda la aplicación.
Flujo request-response
El flujo request-response en Express representa el ciclo completo que sigue una petición HTTP desde que llega al servidor hasta que se envía la respuesta al cliente. Este flujo está íntimamente ligado a la ejecución secuencial del middleware, creando una cadena de procesamiento que transforma y enriquece tanto la petición como la respuesta.
Ciclo de vida de una petición
Cuando un cliente realiza una petición HTTP a una aplicación Express, se inicia un proceso estructurado que atraviesa múltiples etapas. La petición entra al servidor y Express crea los objetos req
y res
, que representan respectivamente la petición entrante y la respuesta que se enviará.
const express = require('express');
const app = express();
// Middleware de logging - primera etapa del flujo
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] Petición recibida: ${req.method} ${req.url}`);
req.startTime = Date.now();
next();
});
// Middleware de procesamiento - segunda etapa
app.use((req, res, next) => {
console.log('Procesando petición...');
req.processed = true;
next();
});
// Manejador de ruta - etapa final
app.get('/users', (req, res) => {
const processingTime = Date.now() - req.startTime;
console.log(`Enviando respuesta después de ${processingTime}ms`);
res.json({
users: ['Ana', 'Carlos', 'María'],
processingTime: processingTime
});
});
Transformación progresiva de los objetos
Durante el flujo, cada middleware puede modificar los objetos req
y res
, añadiendo propiedades, transformando datos o preparando la respuesta. Esta transformación progresiva permite que cada capa del middleware contribuya al procesamiento final:
// Middleware de autenticación - enriquece req
app.use((req, res, next) => {
const token = req.headers.authorization;
if (token) {
req.user = { id: 123, name: 'Usuario Autenticado' };
req.isAuthenticated = true;
}
next();
});
// Middleware de headers personalizados - modifica res
app.use((req, res, next) => {
res.setHeader('X-API-Version', '1.0');
res.setHeader('X-Response-Time', Date.now());
next();
});
// Middleware de validación - puede interrumpir el flujo
app.use('/api/*', (req, res, next) => {
if (!req.isAuthenticated) {
return res.status(401).json({ error: 'Acceso no autorizado' });
}
next();
});
Puntos de terminación del flujo
El flujo request-response puede finalizar en diferentes puntos de la cadena de middleware. Una vez que se envía una respuesta utilizando métodos como res.send()
, res.json()
o res.end()
, el ciclo se completa y no se ejecuta más middleware:
app.use('/health', (req, res, next) => {
// Este middleware termina el flujo aquí
res.status(200).json({ status: 'OK', timestamp: new Date() });
// No se llama a next(), el flujo termina
});
app.use('/data', (req, res, next) => {
if (req.query.format === 'xml') {
res.set('Content-Type', 'application/xml');
return res.send('<data><status>success</status></data>');
}
next(); // Continúa solo si no es XML
});
app.get('/data', (req, res) => {
res.json({ data: 'Información en JSON' });
});
Flujo de respuesta y headers
Durante el flujo, es crucial entender que los headers HTTP deben establecerse antes de enviar el cuerpo de la respuesta. Express permite modificar headers en cualquier punto del middleware hasta que se inicia el envío de datos:
app.use((req, res, next) => {
// Configurar headers antes de la respuesta
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('X-Custom-Header', 'Mi-Valor');
// Interceptar el método json para añadir lógica
const originalJson = res.json;
res.json = function(data) {
console.log('Enviando respuesta JSON:', data);
return originalJson.call(this, data);
};
next();
});
app.get('/api/status', (req, res) => {
// Los headers ya están configurados por el middleware anterior
res.json({
status: 'active',
server: 'Express 5',
timestamp: new Date().toISOString()
});
});
Manejo de flujos asíncronos
En aplicaciones modernas, el middleware frecuentemente realiza operaciones asíncronas como consultas a bases de datos o llamadas a APIs externas. Express 5 maneja mejor las promesas y async/await en el flujo de middleware:
// Middleware asíncrono para validación de datos
app.use('/api/users', async (req, res, next) => {
try {
if (req.method === 'POST') {
// Simulación de validación asíncrona
await new Promise(resolve => setTimeout(resolve, 100));
if (!req.body.email) {
return res.status(400).json({ error: 'Email requerido' });
}
req.validatedData = { ...req.body, validated: true };
}
next();
} catch (error) {
next(error); // Pasar error al middleware de manejo de errores
}
});
// Ruta que utiliza los datos validados
app.post('/api/users', async (req, res) => {
try {
// Usar datos ya validados por el middleware anterior
const userData = req.validatedData;
// Simulación de guardado en base de datos
await new Promise(resolve => setTimeout(resolve, 200));
res.status(201).json({
message: 'Usuario creado exitosamente',
user: userData
});
} catch (error) {
res.status(500).json({ error: 'Error al crear usuario' });
}
});
Flujo de control con múltiples rutas
Express permite que una petición coincida con múltiples definiciones de rutas, ejecutándose en secuencia hasta que una de ellas envíe una respuesta:
// Primera coincidencia - middleware específico de ruta
app.get('/api/data', (req, res, next) => {
console.log('Preparando datos...');
req.preparedData = { source: 'database', cached: false };
next(); // Continuar a la siguiente coincidencia
});
// Segunda coincidencia - procesamiento adicional
app.get('/api/data', (req, res, next) => {
if (req.query.cache === 'true') {
req.preparedData.cached = true;
console.log('Datos cacheados habilitados');
}
next();
});
// Tercera coincidencia - envío final de respuesta
app.get('/api/data', (req, res) => {
res.json({
data: [1, 2, 3, 4, 5],
metadata: req.preparedData
});
});
Este flujo secuencial permite modularizar la lógica de procesamiento, donde cada middleware o manejador de ruta contribuye con una responsabilidad específica al resultado final de la petición.
Otras lecciones de Express
Accede a todas las lecciones de Express y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Expressjs
Introducción Y Entorno
Instalación De Express
Introducción Y Entorno
Estados Http
Routing
Métodos Delete
Routing
Parámetros Y Query Strings
Routing
Métodos Get
Routing
Ejercicios de programación de Express
Evalúa tus conocimientos de esta lección Qué es un Middleware con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.