Creación y uso de middlewares en Express

Lectura
30 min~7 min lectura
CONCEPTO CLAVE: Los middlewares en Express son funciones que tienen acceso al objeto de solicitud (req), al objeto de respuesta (res) y al siguiente middleware en el ciclo de solicitud-respuesta de la aplicación. Son fundamentales para modularizar la lógica de procesamiento de peticiones en una API REST.

Creación y Uso de Middlewares en Express

En el desarrollo de APIs RESTful con Node.js y Express, los middlewares constituyen uno de los conceptos más poderosos y versátiles del framework. Permiten crear capas de procesamiento que se ejecutan de forma secuencial, facilitando la separación de preocupaciones y la reutilización de código.

¿Qué es un Middleware?

Un middleware es esencialmente una función que recibe tres parámetros: request, response y next. Cada middleware puede:

  • Ejecutar cualquier código.
  • Realizar cambios en los objetos request y response.
  • Terminar el ciclo de solicitud-respuesta.
  • Llamar al siguiente middleware en la pila.
📌 Recuerda: El orden de definición de los middlewares en Express importa enormemente. Se ejecutan en el orden en que se definen, por lo que debes estructurarlos cuidadosamente.

Tipos de Middlewares en Express

Tipo Descripción Ejemplo de uso
Integrado Incluido con Express express.json(), express.static()
De terceros Instalados via npm morgan, cors, helmet
Nivel de aplicación Aplica a todas las rutas Autenticación global
Nivel de router Aplica a rutas específicas Middleware por grupo de rutas
Nivel de manejo de errores Con firma de 4 parámetros Gestión centralizada de errores

Creando tu Primer Middleware Personalizado

Veamos cómo crear un middleware desde cero. Empezaremos con un ejemplo sencillo que registre información de cada petición:

// middleware/logger.js
const loggerMiddleware = (req, res, next) => {
  const timestamp = new Date().toISOString();
  const method = req.method;
  const url = req.originalUrl;
  
  console.log(`[${timestamp}] ${method} ${url}`);
  
  // No olvides llamar a next() para continuar
  next();
};

module.exports = loggerMiddleware;
💡 Consejo práctico: Siempre verifica que tu middleware llame a next() o envie una respuesta. Olvidar esto provocará que la petición quede colgada indefinidamente.

Aplicando Middlewares en tu Aplicación

Ahora veamos cómo usar este middleware en tu servidor Express:

// app.js
const express = require('express');
const loggerMiddleware = require('./middleware/logger');

const app = express();

// Middleware a nivel de aplicación - se ejecuta en TODAS las peticiones
app.use(loggerMiddleware);

// También puedes pasar opciones a tu middleware
app.use('/api', loggerMiddleware);

app.get('/users', (req, res) => {
  res.json({ users: [] });
});

app.listen(3000, () => {
  console.log('Servidor corriendo en puerto 3000');
});

Middlewares con Parámetros

A veces necesitas que tu middleware acepte configuraciones. Para esto, puedes factory functions:

// middleware/validateAuth.js
const validateAuth = (options = {}) => {
  const { required = true, roles = [] } = options;
  
  return (req, res, next) => {
    const token = req.headers.authorization;
    
    if (!token && required) {
      return res.status(401).json({ 
        error: 'Token de autenticación requerido' 
      });
    }
    
    // Lógica de validación aquí...
    
    req.user = { id: 1, role: 'admin' }; // Simulación
    next();
  };
};

module.exports = validateAuth;

// Uso:
app.get('/admin', validateAuth({ roles: ['admin'] }), (req, res) => {
  res.json({ message: 'Zona administrativa' });
});
📌 Patrón Factory: Este patrón de crear funciones que retornan middlewares es extremadamente útil para crear middlewares configurables y reutilizables.

Middlewares de Validación de Datos

Un caso de uso muy común es la validación de datos de entrada. Veamos un middleware para validar el cuerpo de una solicitud:

// middleware/validateUser.js
const validateUser = (req, res, next) => {
  const { name, email, password } = req.body;
  const errors = [];
  
  // Validar nombre
  if (!name || typeof name !== 'string' || name.length < 2) {
    errors.push('El nombre debe tener al menos 2 caracteres');
  }
  
  // Validar email con expresión regular
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!email || !emailRegex.test(email)) {
    errors.push('Email inválido');
  }
  
  // Validar contraseña
  if (!password || password.length < 8) {
    errors.push('La contraseña debe tener al menos 8 caracteres');
  }
  
  if (errors.length > 0) {
    return res.status(400).json({ errors });
  }
  
  next();
};

// Uso en ruta
app.post('/users', validateUser, (req, res) => {
  // Aquí ya sabes que los datos son válidos
  const user = req.body;
  res.status(201).json({ message: 'Usuario creado', user });
});
  1. Define el middleware con acceso a req, res y next.
  2. Extrae los datos a validar del objeto request.
  3. Aplica tus reglas de validación.
  4. Si hay errores, responde inmediatamente con status 400.
  5. Si todo está bien, llama a next() para continuar.

Middlewares Asíncronos

En aplicaciones reales, frecuentemente necesitarás realizar operaciones asíncronas dentro de tus middlewares:

// middleware/checkDatabase.js
const checkDatabase = async (req, res, next) => {
  try {
    // Simulación de consulta a base de datos
    const dbConnected = await verifyDatabaseConnection();
    
    if (!dbConnected) {
      return res.status(503).json({ 
        error: 'Servicio de base de datos no disponible' 
      });
    }
    
    req.dbConnected = true;
    next();
  } catch (error) {
    next(error); // Pasa el error al siguiente middleware de errores
  }
};

module.exports = checkDatabase;
⚠️ Importante: Si tu middleware es asíncrono y ocurre un error, debes pasarlo a next(error). Express lo capturará y lo enviará al middleware de manejo de errores.

Middleware de Manejo de Errores

Express tiene un tipo especial de middleware con cuatro parámetros que se encarga de manejar errores:

// middleware/errorHandler.js
const errorHandler = (err, req, res, next) => {
  console.error('Error:', err.stack);
  
  const statusCode = err.statusCode || 500;
  const message = err.message || 'Error interno del servidor';
  
  res.status(statusCode).json({
    error: {
      message,
      status: statusCode,
      timestamp: new Date().toISOString(),
      path: req.path
    }
  });
};

module.exports = errorHandler;

// Uso - debe ser el ÚLTIMO middleware registrado
app.use(errorHandler);
Un middleware de manejo de errores debe tener siempre cuatro parámetros. Express reconoce esta firma especial y sabe que se trata de un handler de errores.

Encadenando Middlewares

Puedes aplicar múltiples middlewares a una misma ruta. Se ejecutarán en orden:

app.post(
  '/products',
  validateProduct,      // 1. Valida los datos
  checkPermissions,    // 2. Verifica permisos del usuario
  checkDatabase,       // 3. Verifica conexión a BD
  async (req, res) => { // 4. Handler final
    const product = await ProductService.create(req.body);
    res.status(201).json(product);
  }
);

Middlewares de Terceros Útiles

Ver más

Existen muchos middlewares populares que puedes instalar via npm:

  • morgan: Logger HTTP para desarrollo y producción.
  • cors: Habilita CORS (Cross-Origin Resource Sharing).
  • helmet: Configura headers de seguridad HTTP.
  • express-rate-limit: Limita las solicitudes repetidas.
  • express-validator: Validación y sanitización de datos.
// Instalación
npm install morgan cors helmet express-rate-limit

// Uso básico
const morgan = require('morgan');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');

app.use(morgan('dev'));
app.use(cors());
app.use(helmet());

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutos
  max: 100 // límite de 100 solicitudes por ventana
});
app.use('/api', limiter);

Organización de Middlewares

Para proyectos grandes, organiza tus middlewares en carpetas temáticas:

/src
  /middleware
    /auth
      validateToken.js
      requireRole.js
    /validation
      validateUser.js
      validateProduct.js
    /security
      rateLimiter.js
      cors.js
    /logging
      logger.js
      requestId.js
    errorHandler.js
  app.js
💡 Best Practice: Mantén tus middlewares pequeños y con una única responsabilidad. Si un middleware hace demasiadas cosas, divídelo en múltiples middlewares más pequeños.

Resumen Práctico

Patrón Sintaxis Uso
Básico (req, res, next) => {} Logging, parsing simple
Con opciones (options) => (req, res, next) => {} Configuraciones dinámicas
De errores (err, req, res, next) => {} Manejo centralizado de errores
Async async (req, res, next) => {} Operaciones con BD o APIs externas

Los middlewares son el alma de Express. Dominar su uso te permitirá crear APIs robustas, mantenibles y escalables. Practica creando tus propios middlewares y verás cómo tu código se vuelve mucho más limpio y modular.

🧠 Quiz

¿Cuál es el orden correcto de los parámetros en un middleware de manejo de errores de Express?

  • A) req, res, next, error
  • B) error, req, res, next
  • C) req, error, res, next
  • D) next, req, res, error
✅ Respuesta correcta: B. Los middlewares de manejo de errores siempre reciben (error, req, res, next) como parámetros. Express identifica este middleware por su firma de cuatro parámetros.
🧠 Quiz

¿Qué sucede si un middleware no llama a next() ni envía una respuesta?

  • A) El servidor se reinicia automáticamente
  • B) La petición queda colgada indefinidamente
  • C) Express ignora el middleware automáticamente
  • D) Se genera un error 500 inmediatamente
✅ Respuesta correcta: B. Si un middleware no llama a next() ni envía una respuesta (res.send(), res.json(), etc.), la solicitud quedará esperando indefinidamente, causando un memory leak y eventualmente un timeout.