Manejo de solicitudes HTTP y códigos de estado

Lectura
25 min~8 min lectura

Manejo de Solicitudes HTTP y Códigos de Estado en Express

En esta lección aprenderás a manejar eficientemente las solicitudes HTTP entrantes en tu aplicación Express, comprendiendo cómo extraer datos, enviar respuestas apropiadas y utilizar los códigos de estado correctamente para comunicar el resultado de cada operación al cliente.

CONCEPTO CLAVE: Cada solicitud HTTP que recibe tu API debe resultar en una respuesta que incluya un código de estado numérico. Este código es la primera información que el cliente utiliza para determinar si la operación fue exitosa, si necesita redirigirse, o si ocurrió algún error.

Los Métodos HTTP en Express

Express proporciona métodos correspondientes a los verbos HTTP principales. Cada método define cómo tu API debe responder a un tipo específico de solicitud.

Método Propósito ¿Tiene Body? Uso Típico
GET Recuperar recursos No Obtener lista de usuarios, detalles de un producto
POST Crear nuevos recursos Registrar un usuario, crear una orden
PUT Reemplazar completamente un recurso Actualizar todos los datos de un perfil
PATCH Modificar parcialmente un recurso Cambiar solo el email de un usuario
DELETE Eliminar un recurso Opcional Eliminar una cuenta, cancelar una reserva
📌 La diferencia entre PUT y PATCH es fundamental: PUT reemplaza todo el recurso (si envías solo nombre, lose), mientras que PATCH solo actualiza los campos enviados.

Extrayendo Datos de las Solicitudes

Express proporciona tres formas principales de acceder a los datos enviados por el cliente:

1. Parámetros de Ruta (route parameters)

Los parámetros de ruta se definen en la URL con dos puntos (:) y son ideales para identificar recursos específicos:

const express = require('express');
const app = express();

// El parámetro :id captura el valor en req.params.id
app.get('/usuarios/:id', (req, res) => {
  const userId = req.params.id;
  
  // Buscar usuario por ID...
  res.json({
    id: userId,
    nombre: 'María García',
    email: '[email protected]'
  });
});

// Múltiples parámetros
app.get('/usuarios/:userId/posts/:postId', (req, res) => {
  const { userId, postId } = req.params;
  res.json({ userId, postId });
});

2. Parámetros de Consulta (query parameters)

Los query parameters aparecen después del símbolo ? en la URL y se acceden mediante req.query. Son ideales para filtrado, paginación y ordenamiento:

// URL: /productos?categoria=electronica&orden=precio&limite=10
app.get('/productos', (req, res) => {
  const { categoria, orden, limite, offset = 0 } = req.query;
  
  console.log('Categoría:', categoria);      // "electronica"
  console.log('Orden:', orden);               // "precio"
  console.log('Límite:', limite);             // "10"
  console.log('Offset:', offset);             // "0"
  
  // Los valores siempre son strings, debemos convertirlos
  const limiteNumerico = parseInt(limite, 10);
  
  res.json({
    filtros: { categoria, orden },
    pagination: { limite: limiteNumerico, offset: parseInt(offset) }
  });
});
💡 Utiliza parseInt() o Number() para convertir query parameters a números, ya que siempre llegan como strings. Considera usar valores por defecto para evitar NaN.

3. Cuerpo de la Solicitud (request body)

Para procesar el cuerpo de las solicitudes POST, PUT y PATCH, necesitas configurar el middleware de parsing de Express. Express incluye middleware integrado para esto:

const express = require('express');
const app = express();

// Middleware para parsing JSON
app.use(express.json());

// Middleware para parsing URL-encoded (formularios HTML)
app.use(express.urlencoded({ extended: true }));

app.post('/usuarios', (req, res) => {
  const { nombre, email, edad } = req.body;
  
  // Validar datos recibidos
  if (!nombre || !email) {
    return res.status(400).json({
      error: 'Faltan campos requeridos',
      camposRequeridos: ['nombre', 'email']
    });
  }
  
  // Crear usuario...
  const nuevoUsuario = {
    id: Date.now(),
    nombre,
    email,
    edad,
    creadoEn: new Date().toISOString()
  };
  
  res.status(201).json(nuevoUsuario);
});
⚠️ IMPORTANTE: Siempre coloca express.json() antes de tus rutas. De lo contrario, req.body estará vacío cuando intentes acceder a ella.

Códigos de Estado HTTP

Los códigos de estado HTTP son esenciales para una comunicación efectiva entre tu API y los clientes. Express utiliza el método res.status() para establecerlos:

Códigos de Éxito (2xx)

Código Nombre Uso
200 OK La solicitud se realizó correctamente (GET, PUT, DELETE)
201 Created Se creó un nuevo recurso exitosamente (POST)
204 No Content Éxito pero no hay contenido que devolver (DELETE exitoso)

Códigos de Error del Cliente (4xx)

Código Nombre Uso
400 Bad Request Datos inválidos o mal formados en la solicitud
401 Unauthorized El cliente no está autenticado
403 Forbidden El cliente no tiene permisos para el recurso
404 Not Found El recurso solicitado no existe
409 Conflict Conflicto con el estado actual del recurso (ej: email duplicado)
422 Unprocessable Entity La sintaxis es correcta pero la semántica no (validación fallida)

Códigos de Error del Servidor (5xx)

Código Nombre Uso
500 Internal Server Error Error inesperado en el servidor
502 Bad Gateway Respuesta inválida de un servidor upstream
503 Service Unavailable El servidor no está disponible temporalmente
📌 NUNCA devuelvas códigos 5xx intencionalmente en tu lógica de aplicación. Estos deben reservarse para errores genuinos del servidor que no puedes controlar.

Patrones para Respuestas Conscientes del Estado

Veamos cómo estructurar respuestas de manera profesional con códigos de estado apropiados:

const usuarios = new Map();

// GET /usuarios - Listar todos
app.get('/usuarios', (req, res) => {
  const lista = Array.from(usuarios.values());
  res.status(200).json({
    success: true,
    data: lista,
    total: lista.length
  });
});

// GET /usuarios/:id - Obtener uno
app.get('/usuarios/:id', (req, res) => {
  const usuario = usuarios.get(req.params.id);
  
  if (!usuario) {
    return res.status(404).json({
      success: false,
      error: 'Usuario no encontrado',
      codigo: 'USUARIO_NO_ENCONTRADO'
    });
  }
  
  res.status(200).json({
    success: true,
    data: usuario
  });
});

// POST /usuarios - Crear
app.post('/usuarios', (req, res) => {
  const { nombre, email } = req.body;
  
  // Validación
  if (!nombre || !email) {
    return res.status(400).json({
      success: false,
      error: 'Datos inválidos',
      detalles: {
        nombre: !nombre ? 'Requerido' : null,
        email: !email ? 'Requerido' : null
      }
    });
  }
  
  // Verificar email único
  const emailExistente = Array.from(usuarios.values())
    .find(u => u.email === email);
  
  if (emailExistente) {
    return res.status(409).json({
      success: false,
      error: 'El email ya está registrado',
      codigo: 'EMAIL_DUPLICADO'
    });
  }
  
  const nuevoUsuario = {
    id: crypto.randomUUID(),
    nombre,
    email,
    creadoEn: new Date().toISOString()
  };
  
  usuarios.set(nuevoUsuario.id, nuevoUsuario);
  
  res.status(201).json({
    success: true,
    data: nuevoUsuario
  });
});

// DELETE /usuarios/:id - Eliminar
app.delete('/usuarios/:id', (req, res) => {
  const { id } = req.params;
  
  if (!usuarios.has(id)) {
    return res.status(404).json({
      success: false,
      error: 'Usuario no encontrado'
    });
  }
  
  usuarios.delete(id);
  
  // 204 No Content: La respuesta no tiene cuerpo
  res.status(204).send();
});
💡 Incluye siempre una estructura consistente en tus respuestas: { success, data, error, codigo }. Esto facilita el manejo del cliente y el debugging.

Manejo Global de Errores

Express permite definir un middleware de manejo de errores que capture todas las excepciones no controladas:

// Middleware de manejo de errores (DEBE tener 4 parámetros)
app.use((err, req, res, next) => {
  console.error('Error:', err.stack);
  
  // Error de validación de sintaxis JSON
  if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
    return res.status(400).json({
      success: false,
      error: 'JSON inválido en el cuerpo de la solicitud',
      detalles: err.message
    });
  }
  
  // Error genérico del servidor
  res.status(err.status || 500).json({
    success: false,
    error: err.message || 'Error interno del servidor',
    codigo: err.codigo || 'ERROR_INTERNO'
  });
});
Ver más: Middleware de logging para debugging
// Middleware que registra todas las solicitudes
app.use((req, res, next) => {
  const inicio = Date.now();
  
  // Ejecutar después de que la respuesta se envía
  res.on('finish', () => {
    const duracion = Date.now() - inicio;
    console.log(
      `${req.method} ${req.originalUrl} - ${res.statusCode} (${duracion}ms)`
    );
  });
  
  next();
});

Este middleware es invaluable durante el desarrollo para identificar solicitudes lentas o con errores.

Estructura de Proyecto Recomendada

Para proyectos más grandes, organiza tu código en módulos:

  1. Crea la estructura de carpetas: src/routes/, src/controllers/, src/middleware/
  2. Separa las rutas: Crea un archivo usuarios.routes.js que exporte las rutas
  3. Implementa los controladores: La lógica de negocio va en archivos usuarios.controller.js
  4. Centraliza el manejo de errores: Un middleware único para todos los errores
  5. Configura CORS: Usa el paquete cors si tu API será consumida desde navegadores
"Una API REST bien diseñada no solo transmite datos, sino que comunica claramente el resultado de cada operación a través de códigos de estado apropiados, mensajes descriptivos y una estructura de respuesta consistente."

Resumen de Mejores Prácticas

  • ✅ Siempre devuelve un código de estado explícito, nunca asumas el default (200)
  • ✅ Usa 201 para recursos creados, 204 para eliminaciones exitosas sin cuerpo
  • ✅ Devuelve 400 con detalles cuando la validación falla
  • ✅ Usa 404 solo cuando el recurso definitivamente no existe
  • ✅ Evita 500; en su lugar, maneja errores específicos con 4xx apropiados
  • ✅ Incluye mensajes de error útiles para el desarrollador
  • ✅ Implementa logging para facilitar el debugging
  • ✅ Usa una estructura de respuesta consistente en toda tu API
🧠 Quiz

¿Cuál es el código de estado correcto para una solicitud POST que crea exitosamente un nuevo recurso en la base de datos?

  • A) 200 OK
  • B) 201 Created
  • C) 204 No Content
  • D) 400 Bad Request
✅ Respuesta: B) 201 Created. El código 201 indica específicamente que la solicitud tuvo éxito y resultedo en la creación de un nuevo recurso. El código 200 se usa para operaciones genéricas exitosas, 204 para éxitos sin cuerpo de respuesta (común en DELETE), y 400 es para errores del cliente.
🧠 Quiz

En Express, ¿cuál de las siguientes es la forma correcta de acceder a los datos enviados en el cuerpo de una solicitud POST que contiene JSON?

  • A) req.body
  • B) req.params
  • C) req.query
  • D) req.data
✅ Respuesta: A) req.body. Para acceder al cuerpo de la solicitud necesitas usar req.body, pero primero debes configurar el middleware express.json(). req.params es para parámetros de ruta, y req.query es para query strings.

En la próxima lección profundizaremos en la validación de datos y sanitización, aprendiendo a usar bibliotecas como Joi y express-validator para garantizar que los datos que recibe tu API sean seguros y estén correctamente estructurados antes de procesarlos.