Errores, validación y seguridad básica
💡 🎯 Al terminar esta lección podrás…
- Distinguir errores
4xxde errores5xx. - Crear un manejador de errores central que no revele detalles internos.
- Validar la entrada y responder
400con una lista de problemas. - Explicar qué es CORS y configurarlo con el paquete
cors. - Aplicar una lista de buenas prácticas básicas de seguridad.
1.Tipos de error en un servidor
En un servidor, los errores no son una excepción: son parte del trabajo. Conviene distinguir dos familias:
| Tipo | De quién es la culpa | Ejemplo | Código |
|---|---|---|---|
| Error del cliente | Quien llama a tu API | Falta un campo, el id no existe, no hay permiso | 4xx (400, 404…) |
| Error del servidor | Tu código o tus dependencias | Un archivo no existe, una API externa falló, un undefined inesperado | 5xx (500, 502…) |
El cliente debe recibir siempre una respuesta útil y coherente, y tú, un registro de lo que pasó. Lo que nunca debe ocurrir es que el servidor se caiga o que la petición se quede colgada.
try/catch en rutas async
Ya conoces try/catch con await. Úsalo cuando puedas recuperarte del error en esa misma ruta:
import { readFile } from "node:fs/promises";
app.get("/config", async (req, res) => {
try {
const texto = await readFile("config.json", "utf8");
res.json(JSON.parse(texto));
} catch (error) {
if (error.code === "ENOENT") {
return res.json({}); // no hay archivo: usamos valores por defecto
}
throw error; // cualquier otro error sube al manejador general
}
});
Fíjate en el throw error: si no sabes arreglar el problema, relánzalo para que lo trate el manejador de errores central, en vez de tragártelo.
2.Un manejador de errores central
En la lección 4 viste que un middleware con cuatro parámetros es un manejador de errores. En Express 5, además, si una función async lanza un error o rechaza una promesa, Express lo envía solo a ese manejador. Así puedes escribir rutas limpias:
export function crearErrorHttp(estado, mensaje) {
const error = new Error(mensaje);
error.status = estado;
return error;
}
export function manejarErrores(err, req, res, next) {
const estado = err.status ?? 500;
const esServidor = estado >= 500;
if (esServidor) console.error(err); // el detalle va al registro, no al cliente
res.status(estado).json({
error: esServidor ? "Error interno del servidor" : err.message,
});
}
import { crearErrorHttp, manejarErrores } from "./errores.js";
app.get("/tareas/:id", async (req, res) => {
const tarea = await buscarTarea(Number(req.params.id)); // si falla, llega al manejador
if (!tarea) throw crearErrorHttp(404, "Tarea no encontrada");
res.json(tarea);
});
// ... resto de rutas ...
app.use((req, res) => res.status(404).json({ error: "Ruta no encontrada" }));
app.use(manejarErrores); // siempre el último
⚠️ Importante · No enseñes los detalles internos
Un mensaje como ECONNREFUSED 10.0.0.5:5432 o un stack trace le dice a un atacante cómo está montado tu servidor. A los errores 5xx responde con un texto genérico y guarda el detalle en la consola o en un registro.
📌 Nota · Express 4
En Express 4 los errores de rutas async no llegaban solos al manejador: había que escribir try/catch y llamar a next(error) en cada ruta. Verás ese estilo en tutoriales antiguos.
Para que un error no tumbe todo el proceso, Node también permite escuchar fallos globales, pero eso se ve en proyectos grandes. Lo importante ahora es que cada ruta tenga una salida segura. Más detalles en la guía de manejo de errores de Express.
3.Validar la entrada
La regla de oro: todo lo que llega de fuera es sospechoso. Puede venir vacío, con el tipo equivocado, enorme o con mala intención. Validar significa comprobar los datos antes de usarlos y rechazar con 400 si no cumplen.
export function validarTarea(datos) {
const errores = [];
if (typeof datos?.titulo !== "string" || datos.titulo.trim() === "") {
errores.push("titulo obligatorio");
} else if (datos.titulo.length > 100) {
errores.push("titulo demasiado largo");
}
if (datos.hecha !== undefined && typeof datos.hecha !== "boolean") {
errores.push("hecha debe ser booleano");
}
return errores;
}
app.post("/tareas", (req, res) => {
const errores = validarTarea(req.body);
if (errores.length > 0) {
return res.status(400).json({ error: "Datos inválidos", detalles: errores });
}
// ... crear la tarea con datos ya comprobados
});
Devolver una lista de problemas (no solo el primero) le ahorra al cliente enviar la petición una y otra vez. Practica con este editor:
💡 Consejo · Cuando el proyecto crezca
Escribir validaciones a mano está bien para empezar. En proyectos grandes se usan librerías de validación (por ejemplo zod) que describen la forma de los datos una sola vez. Se instalan con npm install como viste en la lección 3.
4.CORS y buenas prácticas básicas
Cuando una página web en https://mi-sitio.com intenta llamar a tu API en https://api.mi-sitio.com, el navegador bloquea la respuesta por la política del mismo origen, salvo que tu servidor diga explícitamente que lo permite. Ese permiso se llama CORS (Cross-Origin Resource Sharing) y se da con cabeceras de respuesta. Hay una explicación completa en MDN.
La forma más simple en Express es el paquete oficial cors:
npm install cors
import cors from "cors";
// Solo estos sitios podrán llamar a tu API desde un navegador
app.use(cors({ origin: ["https://mi-sitio.com", "http://localhost:5173"] }));
⚠️ Importante · CORS no es seguridad del servidor
CORS solo le indica a los navegadores qué páginas pueden leer tus respuestas. curl, otro servidor o un script ignoran CORS por completo. Si una ruta es privada, necesita autenticación de verdad, no solo CORS.
Un uso peligroso es cors() sin opciones (permite cualquier origen): vale mientras pruebas, pero en producción limita la lista.
Lista de buenas prácticas básicas
| Práctica | Por qué |
|---|---|
| Validar toda entrada | Evita datos corruptos y muchos ataques. |
Limitar el tamaño del cuerpo: express.json({ limit: "100kb" }) | Impide que alguien te mande una carga enorme. |
| Secretos en variables de entorno | Nunca en el código ni en Git (lección 6). |
| No filtrar errores internos | Los detalles ayudan a un atacante. |
Cabeceras de seguridad con el paquete helmet: app.use(helmet()) | Activa protecciones recomendadas con una línea. |
Limitar peticiones (paquete express-rate-limit) | Frena abusos y fuerza bruta. |
| Usar HTTPS en producción | Cifra los datos en el camino; la plataforma de despliegue suele encargarse. |
Revisar dependencias: npm audit | Detecta vulnerabilidades conocidas. |
| Nunca construir consultas con texto del usuario | Evita la inyección (por ejemplo, SQL) cuando uses bases de datos. |
📌 Nota · Contraseñas y sesiones
Guardar usuarios con contraseña (hashing, tokens, sesiones) es un tema propio y delicado. Si algún día lo necesitas, usa soluciones probadas en lugar de inventar la tuya.
5.Errores frecuentes
- Devolver el stack trace o el mensaje interno al cliente: revela cómo funciona tu servidor.
- Responder 200 con un cuerpo de error: usa el código de estado correcto.
- Tragarse el error en un
catchvacío: el problema desaparece del registro y nadie se entera. - No validar el cuerpo y confiar en que «el cliente enviará lo correcto».
- Creer que CORS protege tu API: solo afecta a los navegadores.
- Dejar
cors()abierto a cualquier origen en producción. - Poner el manejador de errores antes de las rutas: debe ser el último
app.use.
📒 Apuntes para llevar
4xx= culpa del cliente;5xx= culpa del servidor.- Express 5 envía solo al manejador los errores de rutas
async; en Express 4 había que usarnext(err). - Manejador de errores:
(err, req, res, next), siempre al final. - Un error propio puede llevar
statuspara que el manejador responda con el código correcto. - A los
5xxresponde algo genérico y registra el detalle en el servidor. - Valida toda entrada y devuelve una lista de problemas con
400. - CORS permite o niega a páginas de otro origen leer tus respuestas; limita los orígenes.
- Buenas prácticas: validar, limitar el cuerpo, secretos en
.env,helmet, límite de peticiones, HTTPS ynpm audit.
6.Glosario
| Término | Significado |
|---|---|
| Manejador de errores | Middleware de cuatro parámetros que centraliza las respuestas de error. |
| Validar | Comprobar que los datos cumplen las reglas antes de usarlos. |
| Origen | Combinación de protocolo, dominio y puerto de una página web. |
| CORS | Mecanismo con cabeceras que permite a un navegador usar recursos de otro origen. |
| Stack trace | Lista de llamadas que muestra dónde ocurrió un error. |
| Rate limiting | Limitar cuántas peticiones puede hacer un cliente en un tiempo. |
helmet | Paquete que añade cabeceras HTTP de seguridad. |
| Inyección | Ataque que introduce instrucciones maliciosas dentro de un dato de entrada. |
🧠 Comprueba lo aprendido
Elige una respuesta en cada pregunta. Verás al instante si acertaste y por qué.
Un cliente envía un JSON sin el campo obligatorio. ¿Qué código corresponde?
Los datos inválidos son un error del cliente: 400 Bad Request.
¿Cuántos parámetros tiene un manejador de errores de Express?
(err, req, res, next): Express lo reconoce por tener cuatro.
¿Qué debes mostrar al cliente cuando ocurre un error 500?
Los detalles internos ayudan a un atacante; regístralos pero no los expongas.
¿Qué protege realmente CORS?
Solo los navegadores aplican CORS; curl u otro servidor lo ignoran.
¿Cuál es la mejor práctica con una clave secreta de API?
Los secretos van en variables de entorno y en archivos que no se suben a Git.
Tu progreso se guarda en este navegador y se refleja en el índice del curso.
💡 🚀 ¡Ahora te toca!
La práctica fija lo aprendido. Pasa al ejercicio de esta lección.