El middleware en Express.js es una función que intercepta las peticiones HTTP antes de llegar a las rutas. Funciona como una cadena de procesamiento donde cada función puede modificar req/res, ejecutar lógica personalizada o pasar el control al siguiente middleware.
req y res agregando propiedades o métodosnext()Los middlewares actúan como interceptores que procesan cada petición HTTP de forma secuencial. Se ejecutan en el orden definido y permiten implementar funcionalidades transversales como autenticación, CORS, parsing de JSON, logging y manejo de errores sin duplicar código en cada ruta.
Un middleware en Express.js recibe exactamente 3 parámetros y se ejecuta en el flujo de procesamiento de peticiones HTTP:
req (request): El objeto que contiene información sobre la petición HTTP entrante.res (response): El objeto que se utiliza para enviar la respuesta HTTP.next (función): La función que se llama para pasar el control al siguiente middleware en la cadena.// Estructura básica de un middleware
function miMiddleware(req, res, next) {
// Código que se ejecuta
console.log('Middleware ejecutándose...');
// Llamar a next() para continuar al siguiente middleware
next();
} El ciclo de vida de un middleware en Express.js se compone de los siguientes pasos:
req y res, y llamar a next() para pasar el control al siguiente middleware.next() es llamado, el control se pasa al siguiente middleware en la cadena. Si no se llama a next(), la petición se considera “atrapada” y no se enviará ninguna respuesta.const express = require('express');
const app = express();
// Middleware 1: Logger
const logger = (req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // Pasa al siguiente
};
// Middleware 2: Auth
const auth = (req, res, next) => {
const token = req.headers.authorization;
if (!token) return res.status(401).json({ error: 'Sin token' });
next();
};
// Ruta con middlewares
app.get('/perfil', logger, auth, (req, res) => {
res.json({ message: 'Perfil obtenido' });
});
app.listen(3000); Se aplica a toda la aplicación o a rutas específicas usando app.use() o app.METHOD().
const express = require('express');
const app = express();
// Middleware global - se ejecuta en TODAS las rutas
app.use((req, res, next) => {
console.log('Timestamp:', Date.now());
next();
});
// Middleware específico para una ruta
app.use('/users', (req, res, next) => {
console.log('Acceso a /users');
next();
}); Similar al application-level, pero se aplica solo a instancias de express.Router().
const router = express.Router();
// Middleware específico del router
router.use((req, res, next) => {
console.log('Router middleware ejecutándose');
next();
});
router.get('/profile', (req, res) => {
res.send('Perfil de usuario');
});
app.use('/api', router); Maneja errores que ocurren durante la ejecución de la aplicación.
// DEBE tener 4 parámetros: (err, req, res, next)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('¡Algo salió mal!');
}); Middleware incluido con Express.js.
// Servir archivos estáticos
app.use(express.static('public'));
// Parser JSON
app.use(express.json());
// Parser URL-encoded
app.use(express.urlencoded({ extended: true })); Middleware creado por la comunidad.
const morgan = require('morgan');
const cors = require('cors');
const helmet = require('helmet');
app.use(morgan('combined')); // Logging
app.use(cors()); // CORS
app.use(helmet()); // Seguridad // Middleware que registra información de cada petición
const logger = (req, res, next) => {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] ${req.method} ${req.url}`);
next(); // ¡Importante! Continúa al siguiente middleware
};
app.use(logger);
app.get('/', (req, res) => {
res.send('¡Hola Mundo!');
}); Explicación: Este middleware se ejecuta antes de cada ruta y registra la fecha, método HTTP y URL de cada petición.
// Middleware que verifica si el usuario está autenticado
const requireAuth = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Token requerido' });
}
if (token !== 'Bearer mi-token-secreto') {
return res.status(403).json({ error: 'Token inválido' });
}
next(); // Usuario autenticado, continuar
};
// Aplicar solo a rutas protegidas
app.get('/protected', requireAuth, (req, res) => {
res.json({ message: 'Acceso autorizado' });
}); Explicación: Verifica que el usuario tenga un token válido antes de acceder a rutas protegidas.
// Middleware que valida datos del cuerpo de la petición
const validateUser = (req, res, next) => {
const { name, email } = req.body;
if (!name || name.length < 2) {
return res.status(400).json({
error: 'Nombre es requerido y debe tener al menos 2 caracteres'
});
}
if (!email || !email.includes('@')) {
return res.status(400).json({
error: 'Email válido es requerido'
});
}
next(); // Datos válidos, continuar
};
app.post('/users', express.json(), validateUser, (req, res) => {
res.json({ message: 'Usuario creado exitosamente' });
}); Explicación: Valida que los datos enviados en el cuerpo de la petición cumplan con ciertos criterios.
// Middleware que cuenta las visitas a cada ruta
let visitCount = 0;
const countVisits = (req, res, next) => {
visitCount++;
req.visitNumber = visitCount;
console.log(`Visita número: ${visitCount}`);
next();
};
app.use(countVisits);
app.get('/stats', (req, res) => {
res.json({
message: `Esta es la visita número ${req.visitNumber}`,
totalVisits: visitCount
});
}); Explicación: Lleva un registro del número total de peticiones y añade esta información al objeto request.
// Middleware que mide el tiempo de respuesta
const responseTime = (req, res, next) => {
const startTime = Date.now();
// Interceptar el método end() de la respuesta
const originalEnd = res.end;
res.end = function(...args) {
const duration = Date.now() - startTime;
console.log(`${req.method} ${req.url} - ${duration}ms`);
originalEnd.apply(this, args);
};
next();
};
app.use(responseTime);
app.get('/slow', (req, res) => {
// Simular operación lenta
setTimeout(() => {
res.send('Respuesta después de 2 segundos');
}, 2000);
}); Explicación: Mide cuánto tiempo toma procesar cada petición desde que llega hasta que se envía la respuesta.
// Middleware personalizado para manejar CORS
const customCORS = (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// Manejar preflight requests (OPTIONS)
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
};
app.use(customCORS);
app.get('/api/data', (req, res) => {
res.json({ data: 'Accesible desde cualquier origen' });
}); Explicación: Permite que la API sea accesible desde diferentes dominios configurando los headers CORS apropiados.
// Middleware de cache simple
const cache = {};
const simpleCache = (req, res, next) => {
const key = req.originalUrl;
// Si existe en cache, devolver datos cacheados
if (cache[key]) {
console.log('Datos desde cache');
return res.json(cache[key]);
}
// Interceptar res.json para guardar en cache
const originalJson = res.json;
res.json = function(data) {
cache[key] = data;
console.log('Datos guardados en cache');
originalJson.call(this, data);
};
next();
};
app.get('/expensive-operation', simpleCache, (req, res) => {
// Simular operación costosa
setTimeout(() => {
res.json({
result: 'Operación completada',
timestamp: new Date().toISOString()
});
}, 3000);
}); Explicación: Guarda las respuestas en memoria para servirlas más rápido en peticiones futuras a la misma URL.
// Middleware simple de rate limiting
const requests = {};
const rateLimiter = (limit = 10, windowMs = 60000) => {
return (req, res, next) => {
const ip = req.ip || req.connection.remoteAddress;
const now = Date.now();
// Limpiar requests antiguos
if (!requests[ip]) {
requests[ip] = [];
}
requests[ip] = requests[ip].filter(time => now - time < windowMs);
// Verificar límite
if (requests[ip].length >= limit) {
return res.status(429).json({
error: 'Demasiadas peticiones. Intenta más tarde.'
});
}
requests[ip].push(now);
next();
};
};
app.use(rateLimiter(5, 60000)); // 5 requests por minuto
app.get('/limited', (req, res) => {
res.json({ message: 'Petición exitosa' });
}); Explicación: Limita el número de peticiones que un cliente puede hacer en un período de tiempo específico.
// Middleware que detecta el tipo de cliente
const detectClient = (req, res, next) => {
const userAgent = req.headers['user-agent'] || '';
req.clientInfo = {
isMobile: /Mobile|Android|iPhone|iPad/i.test(userAgent),
isBot: /bot|crawl|spider|scraping/i.test(userAgent),
browser: 'Desconocido'
};
// Detectar navegador
if (userAgent.includes('Chrome')) req.clientInfo.browser = 'Chrome';
else if (userAgent.includes('Firefox')) req.clientInfo.browser = 'Firefox';
else if (userAgent.includes('Safari')) req.clientInfo.browser = 'Safari';
console.log(`Cliente: ${req.clientInfo.browser}, Móvil: ${req.clientInfo.isMobile}`);
next();
};
app.use(detectClient);
app.get('/client-info', (req, res) => {
res.json({
message: 'Información del cliente',
clientInfo: req.clientInfo
});
}); Explicación: Analiza el User-Agent para determinar qué tipo de dispositivo y navegador está usando el cliente.
// Middleware de manejo de errores (debe ir al final)
const errorHandler = (err, req, res, next) => {
console.error('Error capturado:', err.message);
// Error de validación
if (err.name === 'ValidationError') {
return res.status(400).json({
error: 'Error de validación',
details: err.message
});
}
// Error de base de datos
if (err.code === 11000) {
return res.status(409).json({
error: 'Recurso duplicado',
details: 'Este elemento ya existe'
});
}
// Error genérico del servidor
res.status(500).json({
error: 'Error interno del servidor',
message: 'Algo salió mal. Por favor, intenta más tarde.'
});
};
// Ruta que puede generar error
app.get('/error-test', (req, res, next) => {
const shouldError = req.query.error === 'true';
if (shouldError) {
const error = new Error('Error de prueba');
error.name = 'ValidationError';
return next(error); // Pasar error al middleware de manejo
}
res.json({ message: 'Sin errores' });
});
// Aplicar middleware de errores (SIEMPRE al final)
app.use(errorHandler); Explicación: Maneja diferentes tipos de errores de forma centralizada, proporcionando respuestas apropiadas según el tipo de error.
const express = require('express');
const app = express();
// 1. Middleware global de logging
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
});
// 2. Middleware para parsing JSON
app.use(express.json());
// 3. Middleware de autenticación para rutas específicas
const requireAuth = (req, res, next) => {
const token = req.headers.authorization;
if (token === 'Bearer secret') {
next();
} else {
res.status(401).json({ error: 'No autorizado' });
}
};
// Rutas públicas
app.get('/', (req, res) => {
res.json({ message: 'API funcionando' });
});
// Rutas protegidas
app.get('/protected', requireAuth, (req, res) => {
res.json({ message: 'Datos protegidos', user: 'admin' });
});
// 4. Middleware de manejo de errores (al final)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Error interno del servidor' });
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Servidor corriendo en puerto ${PORT}`);
}); El orden importa mucho en Express.js:
// 1. Middleware global primero
app.use(logger);
// 2. Middleware específico después
app.use('/api', authMiddleware);
// 3. Rutas después del middleware
app.get('/api/users', getUsers);
// 4. Middleware de errores AL FINAL
app.use(errorHandler);