Express

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ícate

Concepto 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.

Aprende Express online

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.

Accede GRATIS a Express y certifícate

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.