Complete "Lección 4: Gestión de datos de solicitud y creación de endpoints CRUD" primero para desbloquear esta lección.Ir a la lección anterior

Introducción

Toda API recibe datos que no creó. Los usuarios envían JSON malformado, campos faltantes, cadenas de texto donde deberían ir números, y payloads que son técnicamente válidos pero lógicamente incorrectos. Sin una capa de validación, esos problemas corrompen silenciosamente tu base de datos o provocan errores 500 crípticos. Sin una capa de manejo de errores consistente, cada ruta hace algo diferente cuando algo falla: algunas devuelven cadenas de texto simples, otras devuelven páginas de error HTML, otras no devuelven nada en absoluto.

Esta lección construye ambas capas: validación de solicitudes usando la librería express-validator y un manejador de errores centralizado que da a cada respuesta de error la misma forma predecible.

Validación de los Datos de las Solicitudes Entrantes

express-validator envuelve la conocida librería validator.js y se integra directamente como middleware en Express. Instálala junto a tu proyecto:

bash
npm install express-validator

Las reglas de validación se definen como un array de middleware y se adjuntan a una ruta antes de que se ejecute el handler. Aquí tienes una ruta POST /users que requiere un email válido y una contraseña de al menos ocho caracteres:

javascript
const { body, validationResult } = require('express-validator');
const createUserRules = [
body('email').isEmail().withMessage('Must be a valid email address'),
body('password')
.isLength({ min: 8 })
.withMessage('Password must be at least 8 characters'),
];
router.post('/users', createUserRules, (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
// safe to use req.body here
res.status(201).json({ message: 'User created' });
});

Llamar a validationResult(req) recopila cada violación que encontró la cadena de reglas. Si el array no está vacío, la solicitud se rechaza inmediatamente con HTTP 422 Unprocessable Entity, el estado semánticamente correcto cuando el servidor entiende la solicitud pero no puede actuar sobre ella porque los datos son inválidos.

Diagrama de flujo que muestra el recorrido de la solicitud: solicitud POST entrante -> cadena de middleware de validación -> comprobación de validationResult -> si hay errores, devuelve respuesta 422; si no hay errores, continúa al handler de la ruta

Puedes extraer las reglas de validación a un archivo separado a medida que crece el número de rutas, manteniendo los archivos de rutas enfocados solo en la lógica de negocio.

Construcción de un Manejador de Errores Centralizado

Express tiene una firma especial de middleware para el manejo de errores: cuatro parámetros en lugar de tres. Cualquier middleware o ruta que llame a next(err) o lance un error dentro de un wrapper asíncrono saltará el middleware normal y terminará aquí.

Crea un archivo dedicado para ello:

javascript
// middleware/errorHandler.js
function errorHandler(err, req, res, next) {
const statusCode = err.statusCode || 500;
const message = err.message || 'Internal Server Error';
res.status(statusCode).json({
error: {
message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
},
});
}
module.exports = errorHandler;

Regístralo después de todas tus rutas en el archivo principal de la app. El orden importa: Express solo invoca este handler si se define al final:

javascript
const errorHandler = require('./middleware/errorHandler');
app.use('/api', router);
app.use(errorHandler); // must come after all routes

Ahora cualquier ruta puede señalar un problema adjuntando un statusCode a un objeto Error y pasándoselo a next:

javascript
router.get('/users/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
const err = new Error('User not found');
err.statusCode = 404;
return next(err);
}
res.json(user);
} catch (error) {
next(error); // unexpected errors get 500
}
});

Cada error, esperado o no, ahora fluye a través del mismo handler y devuelve la misma estructura JSON. Los equipos de frontend pueden escribir una única función de parseo de errores en lugar de adivinar qué devuelve cada endpoint.

Creación de una Clase AppError Personalizada

Pasar objetos Error planos con propiedades statusCode añadidas manualmente se vuelve repetitivo. Una clase personalizada pequeña lo simplifica:

javascript
// utils/AppError.js
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true; // distinguishes expected errors from bugs
}
}
module.exports = AppError;

El flag isOperational permite que tu manejador de errores se comporte de forma diferente para los errores que lanzaste deliberadamente frente a caídas inesperadas en tiempo de ejecución:

javascript
const AppError = require('../utils/AppError');
// in a route
if (!user) {
return next(new AppError('User not found', 404));
}

Puedes comprobar isOperational en el manejador de errores para decidir si registrar el stack trace completo, enviar una alerta o simplemente devolver el mensaje tal cual.

Diagrama que muestra dos rutas de error: error operacional (isOperational true) que va a una respuesta JSON segura, y error inesperado (isOperational false) que va al paso de logging/alerting y luego a una respuesta 500 genérica

Errores Comunes

Colocar el manejador de errores antes de tus rutas es el error más frecuente: nunca se activa porque aún no se ha pasado ningún error. Regístralo siempre al final.

Usar HTTP 400 para cada error del cliente es impreciso. Usa 400 para sintaxis malformada, 401 cuando falta autenticación, 403 cuando el usuario está autenticado pero no tiene permiso, 404 cuando un recurso no existe y 422 cuando la estructura es válida pero el contenido falla las reglas de negocio.

Filtrar stack traces en producción es un riesgo real de seguridad. El spread condicional en el ejemplo de errorHandler de arriba (process.env.NODE_ENV === 'development') garantiza que los stacks solo aparezcan en local.

Olvidar envolver los handlers asíncronos de las rutas en try/catch hace que los rechazos de promesas no controlados salten por completo tu manejador de errores. Un pequeño wrapper asíncrono puede imponer este patrón en todas las rutas sin escribir boilerplate.

Resumen

La validación con express-validator detiene los datos incorrectos en la puerta y devuelve feedback estructurado al cliente. Un manejador de errores centralizado con una estructura JSON consistente hace que tu API sea predecible para los consumidores y más fácil de mantener a medida que crece. La clase AppError conecta ambas necesidades: te ofrece una forma limpia de señalar fallos esperados desde cualquier punto de tu código y permite que un único manejador decida cómo responder.

Práctica de la lección

Punto de control de la lección

Repasa lo aprendido y recibe comentarios sobre tu trabajo.

0/1
Respondidas · 0/1
Lección 5: Validación de Entradas y Manejo de Errores