Express

Express

Tutorial Express: Control de flujo con next()

Aprende a controlar el flujo y manejar errores en Express usando next() para middleware y rutas de forma eficiente y profesional.

Aprende Express y certifícate

Control de flujo

El control de flujo en Express se refiere a la capacidad de dirigir el procesamiento de una petición HTTP a través de diferentes middleware y rutas. La función next() actúa como el mecanismo principal para transferir el control entre estos componentes, permitiendo que cada middleware decida si continúa el procesamiento o lo detiene.

Cuando Express recibe una petición, esta debe atravesar una cadena de middleware hasta llegar a su destino final. Cada middleware en esta cadena tiene tres opciones: procesar la petición y continuar, procesar y finalizar la respuesta, o transferir el control al siguiente elemento de la cadena.

Transferencia básica de control

La función next() sin argumentos indica que el middleware actual ha completado su trabajo y que el procesamiento debe continuar con el siguiente middleware o ruta en la pila:

app.use((req, res, next) => {
  console.log('Middleware 1 ejecutado');
  next(); // Continúa al siguiente middleware
});

app.use((req, res, next) => {
  console.log('Middleware 2 ejecutado');
  next(); // Continúa al siguiente middleware
});

app.get('/', (req, res) => {
  res.send('Respuesta final');
});

En este ejemplo, ambos middleware se ejecutan secuencialmente antes de que la ruta maneje la petición. Si cualquiera de ellos omite la llamada a next(), el flujo se detiene y la petición queda colgada.

Control condicional del flujo

Los middleware pueden implementar lógica condicional para determinar si el procesamiento debe continuar o detenerse:

app.use('/admin', (req, res, next) => {
  const isAuthenticated = req.headers.authorization;
  
  if (isAuthenticated) {
    console.log('Usuario autenticado, continuando...');
    next(); // Permite continuar
  } else {
    res.status(401).json({ error: 'No autorizado' });
    // No se llama a next(), el flujo se detiene aquí
  }
});

app.get('/admin/dashboard', (req, res) => {
  res.json({ message: 'Panel de administración' });
});

Este patrón es fundamental para implementar sistemas de autorización y validación, donde el middleware actúa como un filtro que determina si la petición puede proceder.

Salto de middleware con next('route')

Express proporciona un mecanismo especial para saltar middleware específicos de una ruta utilizando next('route'). Esta funcionalidad permite omitir el resto de middleware asociados a una ruta particular:

app.get('/usuarios/:id', (req, res, next) => {
  if (req.params.id === '0') {
    next('route'); // Salta al siguiente manejador de ruta
  } else {
    next(); // Continúa con el siguiente middleware de esta ruta
  }
}, (req, res, next) => {
  // Este middleware se omite cuando id === '0'
  console.log('Validando usuario específico');
  next();
});

app.get('/usuarios/:id', (req, res) => {
  res.json({ message: 'Usuario por defecto' });
});

Esta técnica es especialmente útil cuando tienes múltiples manejadores para la misma ruta con diferentes condiciones de procesamiento.

Flujo de control en rutas anidadas

Los routers de Express mantienen su propio flujo de control independiente, pero pueden transferir el control de vuelta al router padre:

const userRouter = express.Router();

userRouter.use((req, res, next) => {
  console.log('Middleware específico de usuarios');
  next();
});

userRouter.get('/:id', (req, res, next) => {
  const userId = req.params.id;
  
  if (userId === 'admin') {
    // Transfiere el control de vuelta al router principal
    next('router');
  } else {
    res.json({ userId: userId });
  }
});

app.use('/usuarios', userRouter);

// Este middleware se ejecuta cuando se usa next('router')
app.use((req, res) => {
  res.status(404).json({ error: 'Ruta no encontrada' });
});

El uso de next('router') permite que un router anidado devuelva el control al nivel superior, útil para implementar sistemas de enrutamiento complejos con múltiples niveles de abstracción.

Patrones avanzados de control

Los middleware pueden implementar patrones de control sofisticados combinando diferentes técnicas:

app.use('/api', (req, res, next) => {
  // Middleware de logging
  console.log(`${req.method} ${req.path} - ${new Date().toISOString()}`);
  next();
});

app.use('/api', (req, res, next) => {
  // Middleware de rate limiting
  const clientIP = req.ip;
  const requestCount = getRequestCount(clientIP);
  
  if (requestCount > 100) {
    res.status(429).json({ error: 'Demasiadas peticiones' });
    return; // Detiene el flujo sin llamar a next()
  }
  
  incrementRequestCount(clientIP);
  next();
});

app.use('/api', (req, res, next) => {
  // Middleware de validación de contenido
  if (req.method === 'POST' && !req.headers['content-type']) {
    res.status(400).json({ error: 'Content-Type requerido' });
    return;
  }
  
  next();
});

Este enfoque en capas de middleware permite construir aplicaciones robustas donde cada capa tiene una responsabilidad específica y puede decidir independientemente si el procesamiento debe continuar.

La comprensión del control de flujo es esencial para diseñar arquitecturas de middleware eficientes que sean tanto flexibles como predecibles en su comportamiento.

Manejo de errores con next()

El manejo de errores en Express utiliza la función next() como mecanismo principal para propagar errores a través de la cadena de middleware. Cuando se pasa un argumento a next(), Express interpreta que ha ocurrido un error y activa el sistema de manejo de errores de la aplicación.

Propagación de errores básica

Para activar el manejo de errores, simplemente pasa cualquier valor como argumento a la función next(). Express detectará automáticamente que se trata de un error y saltará todos los middleware normales para buscar el primer middleware de manejo de errores:

app.get('/usuarios/:id', (req, res, next) => {
  const userId = req.params.id;
  
  if (!userId || userId.length < 3) {
    const error = new Error('ID de usuario inválido');
    error.status = 400;
    next(error); // Propaga el error
    return;
  }
  
  // Lógica normal continúa solo si no hay errores
  res.json({ userId: userId });
});

Cuando se ejecuta next(error), Express omite todos los middleware posteriores que no sean específicamente para manejo de errores, dirigiendo el flujo directamente hacia el sistema de gestión de errores.

Middleware de manejo de errores

Los middleware de manejo de errores se distinguen por tener cuatro parámetros en lugar de tres. Express los identifica automáticamente por esta signatura y los trata de forma especial:

// Middleware normal (3 parámetros)
app.use((req, res, next) => {
  // Lógica normal
});

// Middleware de manejo de errores (4 parámetros)
app.use((err, req, res, next) => {
  console.error('Error capturado:', err.message);
  
  const statusCode = err.status || 500;
  res.status(statusCode).json({
    error: err.message,
    timestamp: new Date().toISOString()
  });
});

Es crucial que estos middleware se definan después de todas las rutas y middleware normales, ya que Express los procesa en el orden en que se definen.

Manejo de errores asíncronos

Los errores en operaciones asíncronas requieren atención especial, ya que no se propagan automáticamente a través del sistema de manejo de errores de Express:

app.get('/datos/:id', async (req, res, next) => {
  try {
    const datos = await obtenerDatosDeBaseDeDatos(req.params.id);
    
    if (!datos) {
      const error = new Error('Datos no encontrados');
      error.status = 404;
      throw error;
    }
    
    res.json(datos);
  } catch (error) {
    next(error); // Propaga errores asíncronos manualmente
  }
});

Sin el bloque try-catch y la llamada explícita a next(error), los errores asíncronos no serían capturados por el sistema de manejo de errores de Express.

Errores personalizados y clasificación

Puedes crear clases de error personalizadas para manejar diferentes tipos de errores de forma más granular:

class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.status = 400;
    this.field = field;
  }
}

class DatabaseError extends Error {
  constructor(message) {
    super(message);
    this.name = 'DatabaseError';
    this.status = 500;
  }
}

app.post('/usuarios', (req, res, next) => {
  const { email, password } = req.body;
  
  if (!email || !email.includes('@')) {
    next(new ValidationError('Email inválido', 'email'));
    return;
  }
  
  if (!password || password.length < 8) {
    next(new ValidationError('Contraseña muy corta', 'password'));
    return;
  }
  
  // Continúa con la lógica normal
  res.json({ message: 'Usuario creado' });
});

Middleware de manejo de errores especializado

Puedes implementar múltiples middleware de manejo de errores para tratar diferentes tipos de errores de forma específica:

// Manejo específico para errores de validación
app.use((err, req, res, next) => {
  if (err.name === 'ValidationError') {
    res.status(400).json({
      error: 'Error de validación',
      message: err.message,
      field: err.field
    });
    return;
  }
  next(err); // Pasa al siguiente manejador de errores
});

// Manejo específico para errores de base de datos
app.use((err, req, res, next) => {
  if (err.name === 'DatabaseError') {
    console.error('Error de base de datos:', err);
    res.status(500).json({
      error: 'Error interno del servidor',
      message: 'Problema con la base de datos'
    });
    return;
  }
  next(err);
});

// Manejador de errores general (debe ir al final)
app.use((err, req, res, next) => {
  console.error('Error no manejado:', err);
  res.status(500).json({
    error: 'Error interno del servidor',
    message: 'Algo salió mal'
  });
});

Errores en middleware de terceros

Cuando trabajas con middleware de terceros, es importante capturar y manejar sus errores adecuadamente:

const multer = require('multer');
const upload = multer({ 
  dest: 'uploads/',
  limits: { fileSize: 1000000 } // 1MB
});

app.post('/upload', (req, res, next) => {
  upload.single('archivo')(req, res, (err) => {
    if (err instanceof multer.MulterError) {
      if (err.code === 'LIMIT_FILE_SIZE') {
        const error = new Error('Archivo demasiado grande');
        error.status = 413;
        next(error);
        return;
      }
    } else if (err) {
      next(err);
      return;
    }
    
    // Continúa si no hay errores
    res.json({ message: 'Archivo subido correctamente' });
  });
});

Logging y monitoreo de errores

Un sistema robusto de manejo de errores debe incluir logging detallado para facilitar el debugging y monitoreo:

app.use((err, req, res, next) => {
  // Información del contexto del error
  const errorInfo = {
    message: err.message,
    stack: err.stack,
    url: req.url,
    method: req.method,
    ip: req.ip,
    userAgent: req.get('User-Agent'),
    timestamp: new Date().toISOString()
  };
  
  // Log diferenciado según la severidad
  if (err.status >= 500) {
    console.error('Error crítico:', errorInfo);
  } else {
    console.warn('Error de cliente:', errorInfo);
  }
  
  // Respuesta al cliente (sin exponer detalles internos)
  const isProduction = process.env.NODE_ENV === 'production';
  res.status(err.status || 500).json({
    error: err.message,
    ...(isProduction ? {} : { stack: err.stack })
  });
});

El manejo efectivo de errores con next() es fundamental para crear aplicaciones Express robustas y confiables que puedan recuperarse graciosamente de situaciones inesperadas y proporcionar información útil tanto a los desarrolladores como a los usuarios finales.

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 Control de flujo con next() con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.