Saltar al contenido
Curso de Node.js y APIs
Todos los cursos Ir al ejercicio →

Errores, validación y seguridad básica

  • ⏱ 60 minutos
  • 📶 Nivel: intermedio
  • 🧩 Incluye ejercicio

💡 🎯 Al terminar esta lección podrás…

  • Distinguir errores 4xx de errores 5xx.
  • Crear un manejador de errores central que no revele detalles internos.
  • Validar la entrada y responder 400 con 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:

TipoDe quién es la culpaEjemploCódigo
Error del clienteQuien llama a tu APIFalta un campo, el id no existe, no hay permiso4xx (400, 404…)
Error del servidorTu código o tus dependenciasUn archivo no existe, una API externa falló, un undefined inesperado5xx (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:

index.js
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:

errores.js
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,
  });
}
index.js
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.

validar.js
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;
}
index.js
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:

▶ Un validador
Consola

💡 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:

Terminal
npm install cors
index.js
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ácticaPor qué
Validar toda entradaEvita 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 entornoNunca en el código ni en Git (lección 6).
No filtrar errores internosLos 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ónCifra los datos en el camino; la plataforma de despliegue suele encargarse.
Revisar dependencias: npm auditDetecta vulnerabilidades conocidas.
Nunca construir consultas con texto del usuarioEvita 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

📒 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 usar next(err).
  • Manejador de errores: (err, req, res, next), siempre al final.
  • Un error propio puede llevar status para que el manejador responda con el código correcto.
  • A los 5xx responde 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 y npm audit.

6.Glosario

TérminoSignificado
Manejador de erroresMiddleware de cuatro parámetros que centraliza las respuestas de error.
ValidarComprobar que los datos cumplen las reglas antes de usarlos.
OrigenCombinación de protocolo, dominio y puerto de una página web.
CORSMecanismo con cabeceras que permite a un navegador usar recursos de otro origen.
Stack traceLista de llamadas que muestra dónde ocurrió un error.
Rate limitingLimitar cuántas peticiones puede hacer un cliente en un tiempo.
helmetPaquete que añade cabeceras HTTP de seguridad.
InyecciónAtaque 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?

¿Cuántos parámetros tiene un manejador de errores de Express?

¿Qué debes mostrar al cliente cuando ocurre un error 500?

¿Qué protege realmente CORS?

¿Cuál es la mejor práctica con una clave secreta de API?

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.

Ir al ejercicio