Middlewares en Express

Intermedio
Node
Node
Actualizado: 03/09/2025

¿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 es true, 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 - Autor del tutorial

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.