¿Qué es el Middleware en Express?
El middleware es un concepto fundamental en Express que permite procesar las solicitudes HTTP de manera modular y reutilizable. Cada middleware tiene la capacidad de ejecutar código, modificar los objetos request y response, terminar el ciclo de solicitud-respuesta o llamar al siguiente middleware en la pila.
Cuando Express recibe una petición, la procesa a través de una cadena de middleware antes de llegar a la ruta final. Esto nos permite separar responsabilidades como autenticación, validación, logging y manejo de errores en funciones independientes y reutilizables.
Tipos de Middleware en Express
Express soporta varios tipos de middleware, cada uno con un propósito específico dentro de nuestra aplicación.
1. Middleware de Aplicación
Este tipo de middleware se ejecuta en todas las solicitudes que llegan a tu aplicación, sin importar la ruta o el método HTTP.
const express = require('express');
const app = express();
// Middleware de aplicación - se ejecuta en TODAS las peticiones
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
req.timestamp = Date.now();
next();
});
app.get('/usuarios', (req, res) => {
res.json({ usuarios: [] });
});
app.listen(3000);2. Middleware de Ruta
Se aplica a rutas específicas o grupos de rutas usando path patterns.
// Middleware específico para rutas de /admin
app.use('/admin', (req, res, next) => {
console.log('Middleware para admin');
next();
});
app.get('/admin/dashboard', (req, res) => {
res.json({ mensaje: 'Panel de administración' });
});
// Middleware con parámetros
app.use('/api/:version', (req, res, next) => {
console.log(`API Version: ${req.params.version}`);
next();
});3. Middleware de Manejo de Errores
Tienen cuatro parámetros (err, req, res, next) y se usan exclusivamente para manejar errores.
// MIDDLEWARE DE ERROR - siempre tiene 4 parámetros
app.use((err, req, res, next) => {
console.error('Error occurred:', err.stack);
const statusCode = err.statusCode || 500;
const mensaje = err.message || 'Error interno del servidor';
res.status(statusCode).json({
success: false,
error: {
mensaje,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
}
});
});err como primer argumento.Patrones de Middleware más Utilizados
El middleware de autenticación es crucial para proteger rutas sensibles de tu API. Debe verificar tokens JWT, sesiones u otros mecanismos de autenticación antes de permitir el acceso.
// Middleware de autenticación JWT
const jwt = require('jsonwebtoken');
const authMiddleware = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
success: false,
error: 'Token de autenticación no proporcionado'
});
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded; // Adjuntar usuario al request
next();
} catch (error) {
return res.status(401).json({
success: false,
error: 'Token inválido o expirado'
});
}
};
// Uso: proteger rutas específicas
app.get('/perfil', authMiddleware, (req, res) => {
res.json({ usuario: req.user });
});Separar la validación de datos en middleware dedicado mejora la mantenibilidad y permite reutilizar reglas de validación en diferentes rutas.
// Middleware de validación con express-validator
const { body, validationResult } = require('express-validator');
const validarUsuario = [
body('email')
.isEmail()
.withMessage('Email inválido')
.normalizeEmail(),
body('password')
.isLength({ min: 8 })
.withMessage('La contraseña debe tener al menos 8 caracteres')
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
.withMessage('La contraseña debe contener mayúsculas, minúsculas y números'),
body('nombre')
.trim()
.notEmpty()
.withMessage('El nombre es requerido')
.isLength({ max: 50 })
.withMessage('El nombre no puede exceder 50 caracteres'),
(req, res, next) => {
const errores = validationResult(req);
if (!errores.isEmpty()) {
return res.status(400).json({
success: false,
errores: errores.array().map(e => e.msg)
});
}
next();
}
];
app.post('/usuarios', validarUsuario, (req, res) => {
// Los datos ya están validados y sanitizados
console.log('Datos válidos:', req.body);
res.status(201).json({ success: true });
});Después de la autenticación, usa middleware de autorización para verificar permisos específicos.
// Middleware de autorización por roles
const rolesPermitidos = (...roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({
success: false,
error: 'Autenticación requerida'
});
}
if (!roles.includes(req.user.rol)) {
return res.status(403).json({
success: false,
error: 'No tienes permisos para acceder a este recurso'
});
}
next();
};
};
// Rutas protegidas con diferentes roles
app.delete('/usuarios/:id', authMiddleware, rolesPermitidos('admin'), eliminarUsuario);
app.patch('/configuracion', authMiddleware, rolesPermitidos('admin', 'editor'), actualizarConfig);
app.get('/reportes', authMiddleware, rolesPermitidos('admin', 'editor', 'viewer'), verReportes);Middleware de Terceros Esenciales
Existen varios middlewares de terceros que deberías considerar para cualquier API REST profesional:
| Middleware | Propósito | Alternativa |
|---|---|---|
cors | Configurar Cross-Origin Resource Sharing | Manual con headers |
helmet | Seguridad mediante headers HTTP | Configuración manual |
morgan | Logging de solicitudes HTTP | console.log manual |
compression | Compresión gzip de respuestas | - |
express-rate-limit | Limitar solicitudes por IP | - |
// Instalación: npm install cors helmet morgan compression express-rate-limit
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const app = express();
// Seguridad: helmet establece headers HTTP seguros
app.use(helmet());
// CORS: permitir solicitudes desde dominios específicos
app.use(cors({
origin: ['https://mi-frontend.com', 'https://admin.mi-frontend.com'],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// Rate limiting: limitar a 100 solicitudes por cada IP cada 15 minutos
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100,
message: {
success: false,
error: 'Demasiadas solicitudes desde esta IP, intenta más tarde'
}
});
app.use('/api/', limiter);
// Compression: comprimir respuestas
app.use(compression());
// Logging
app.use(morgan('combined'));Mejores Prácticas para Middleware
- Siempre llama a next() o envía una respuesta: Cada middleware debe terminar el ciclo de request-response llamando a next() o enviando una respuesta. Si olvidas hacerlo, la solicitud quedará colgada indefinidamente.
- Mantén los middlewares pequeños y con una sola responsabilidad: En lugar de un middleware gigante que hace todo, crea varios middlewares pequeños y reutilizables que se compongan.
- Coloca los middlewares en el orden correcto: Express ejecuta los middlewares en el orden en que se definen. Asegúrate de que parseadores de body vengan antes de los de validación.
- Maneja errores asíncronos correctamente: Usa try-catch en funciones asíncronas y pasa los errores al siguiente middleware de error.
- Documenta tu middleware: Añade comentarios JSDoc explicando qué hace cada middleware, qué espera en req y qué modifica.
// ❌ MAL: Middleware que hace demasiado
app.use(async (req, res, next) => {
// Auth, validación, logging, transformaciones... todo junto
const token = req.headers.authorization;
const user = await verifyToken(token);
req.user = user;
const data = validate(req.body);
logRequest(req);
transformData(req);
next();
});
// ✅ BIEN: Middlewares separados y reutilizables
app.use(logRequest); // 1. Logging primero
app.use(helmet()); // 2. Seguridad
app.use(cors()); // 3. CORS
app.use(express.json()); // 4. Parsear body
app.use(authMiddleware); // 5. Autenticación
app.use('/api', apiRoutes); // 6. Rutas APIManejo de Errores Asíncronos
Una fuente común de bugs en Express es el manejo de errores en funciones asíncronas. Si no los manejas correctamente, los errores no llegarán al middleware de errores.
// ❌ MAL: Error async que no se captura
app.get('/usuarios/:id', async (req, res, next) => {
const usuario = await Usuario.findById(req.params.id);
if (!usuario) {
throw new Error('Usuario no encontrado'); // ¡Esto NO funciona!
}
res.json(usuario);
});
// ✅ BIEN: Wrappers para capturar errores async
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/usuarios/:id', asyncHandler(async (req, res, next) => {
const usuario = await Usuario.findById(req.params.id);
if (!usuario) {
const error = new Error('Usuario no encontrado');
error.statusCode = 404;
throw error;
}
res.json(usuario);
}));
// Alternativa con express-async-handler
const asyncHandler = require('express-async-handler');
app.get('/productos/:id', asyncHandler(async (req, res) => {
const producto = await Producto.findById(req.params.id);
if (!producto) {
throw new HttpError(404, 'Producto no encontrado');
}
res.json(producto);
}));Ver más: Middleware de Logging Avanzado con Winstonconst winston = require('winston');
const expressWinston = require('express-winston');
// Middleware de logging para todas las requests
app.use(expressWinston.logger({
transports: [
new winston.transports.File({ filename: 'logs/requests.log' }),
new winston.transports.Console()
],
format: winston.format.combine(
winston.format.colorize(),
winston.format.json()
),
meta: true,
msg: 'HTTP {{req.method}} {{req.url}}',
expressFormat: true,
colorize: false,
// Ignorar logging de health checks
ignoreRoute: (req) => req.url === '/health'
}));Middleware para APIs RESTful
Vamos a crear un conjunto completo de middleware para una API REST profesional:
// middlewares/index.js
// Middleware de parsing (debe ir primero)
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Middleware de utilidad para respuestas
app.use((req, res, next) => {
// Método helper para respuestas exitosas
res.success = (data, statusCode = 200) => {
return res.status(statusCode).json({
success: true,
data
});
};
// Método helper para respuestas con paginación
res.paginated = (data, pagination, statusCode = 200) => {
return res.status(statusCode).json({
success: true,
data,
pagination
});
};
next();
});
// Middleware de logging de requests
app.use((req, res, next) => {
const inicio = Date.now();
res.on('finish', () => {
const duracion = Date.now() - inicio;
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url} - ${res.statusCode} (${duracion}ms)`);
});
next();
});El middleware es la salsa secreta de Express. Dominar su uso te permitirá crear APIs más limpias, mantenibles y profesionales. Recuerda: cada middleware debe hacer una sola cosa y hacerla bien.
¿Cuál es la firma correcta de un middleware de manejo de errores en Express?
- A) (req, res, next) => {}
- B) (err, req, res) => {}
- C) (err, req, res, next) => {}
- D) (error, request, response) => {}
¿Qué ocurre si un middleware no llama a next() ni envía una respuesta?
- A) La solicitud continúa normalmente
- B) Express lanza un error automáticamente
- C) La solicitud queda colgada indefinidamente
- D) Se llama automáticamente al siguiente middleware
¿En qué orden se deben colocar los middlewares en Express?
- A) Primero específicos, luego generalistas
- B) El orden no importa
- C) Primero generalistas, luego específicos
- D) Solo puede haber un middleware por aplicación