← Servidor web y API con Node

Sesión 5 · Semana 2

Crear y validar

Hoy · Hoja de ruta

  1. 1. Aprende: Cómo se recibe un recurso nuevo, se valida y se responde.
  2. 2. Haz: La ruta de creación con validación completa.
  3. 3. Comprueba: Ninguna entrada inválida llega a tocar los datos.

Antes de empezar · 5 minutos, sin apuntes

  1. ¿Qué pasaría si alguien envía un campo id en la creación?
  2. ¿Y un campo que no existe en tu modelo?
  3. ¿Puede el cliente saltarse tu validación de la UD4?

La ruta

export async function crearProducto(peticion, respuesta, next) {
  try {
    const datos = validarProductoNuevo(peticion.body);   // lanza si no vale
    const creado = await servicio.crear(datos);

    respuesta
      .status(201)
      .location(`/api/productos/${creado.id}`)
      .json(aRespuesta(creado));
  } catch (error) {
    next(error);
  }
}

La validación, en tres pasos

const CAMPOS_PERMITIDOS = ["nombre", "precio", "categoria", "stock", "descripcion"];

export function validarProductoNuevo(cuerpo) {
  if (cuerpo === null || typeof cuerpo !== "object" || Array.isArray(cuerpo)) {
    throw new ErrorDeValidacion([{ campo: "cuerpo", mensaje: "Se esperaba un objeto JSON" }]);
  }

  const errores = [];
  const limpio = {};

  // 1 · solo lo permitido entra
  for (const campo of CAMPOS_PERMITIDOS) {
    if (cuerpo[campo] !== undefined) limpio[campo] = cuerpo[campo];
  }

  // 2 · obligatorios y tipos
  if (typeof limpio.nombre !== "string" || limpio.nombre.trim() === "") {
    errores.push({ campo: "nombre", mensaje: "Es obligatorio" });
  }
  const precio = Number(limpio.precio);
  if (Number.isNaN(precio) || precio <= 0) {
    errores.push({ campo: "precio", mensaje: "Debe ser un número mayor que cero" });
  }

  // 3 · todos los errores a la vez
  if (errores.length > 0) throw new ErrorDeValidacion(errores);

  return { ...limpio, nombre: limpio.nombre.trim(), precio };
}

Lista blanca, no lista negra

Copiar el cuerpo entero al objeto guardado —lo que hace un { ...peticion.body } sin filtrar— permite a quien llama meter campos que tú no habías previsto: un id que pisa el tuyo, un rol que no debería poder tocar, o basura que se queda en tus datos para siempre.

Enumera los campos que aceptas y descarta el resto. Es una línea más y cierra una familia entera de agujeros.

El identificador lo pone el servidor

Quien crea no elige el identificador: lo asigna el servidor y lo devuelve. Por eso la respuesta incluye el recurso creado y la cabecera Location con su URL, y por eso id no está entre los campos permitidos.

Recordar de dónde viene esto

Las tres validaciones, otra vez
  1. Nativa · comodidad
  2. Cliente · buena experiencia
  3. Servidor · la única obligatoria

Cualquiera puede llamar a tu API con un cliente HTTP y saltarse las dos primeras. Compruébalo hoy mismo: envía desde tu fichero .http un producto con precio negativo y mira qué pasa.

Tarea 5 · La creación

  1. Implementa POST /api/productos con validación en tres pasos.
  2. Devuelve 201, Location y el recurso creado.
  3. Devuelve 400 con todos los errores, no solo el primero.
  4. Descarta los campos no permitidos y demuéstralo enviando uno de más.
  5. Intenta crear un producto saltándote el formulario y comprueba que la API se defiende.
  6. Comprueba qué ocurre si el cuerpo no es JSON válido.
Objetivo mínimoCreación con validación completa, 201 y Location.
Si lo tienesResponde 409 si ya existe un producto con el mismo nombre.
RetoEscribe la validación como una tabla de reglas declarada como dato, y aplícala en bucle.

Checkpoint · fin de la sesión 5

  • Solo entran los campos permitidos.
  • Se devuelven todos los errores de una vez.
  • El identificador lo asigna el servidor.
  • Has comprobado tú mismo que la validación del cliente se salta.
Ver respuestas

1 · Se descarta: solo se aceptan los campos de la lista.

2 · Un 201 con Location y el recurso creado.

3 · Sí, con cualquier cliente HTTP: por eso la del servidor es la que cuenta.