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.
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 | Sí | Registrar un usuario, crear una orden |
PUT |
Reemplazar completamente un recurso | Sí | Actualizar todos los datos de un perfil |
PATCH |
Modificar parcialmente un recurso | Sí | Cambiar solo el email de un usuario |
DELETE |
Eliminar un recurso | Opcional | Eliminar una cuenta, cancelar una reserva |
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) }
});
});
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);
});
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 |
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();
});
{ 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:
- Crea la estructura de carpetas:
src/routes/,src/controllers/,src/middleware/ - Separa las rutas: Crea un archivo
usuarios.routes.jsque exporte las rutas - Implementa los controladores: La lógica de negocio va en archivos
usuarios.controller.js - Centraliza el manejo de errores: Un middleware único para todos los errores
- Configura CORS: Usa el paquete
corssi 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
¿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
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
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.