Hoy · Hoja de ruta
- 1. Aprende: Cómo se recibe un recurso nuevo, se valida y se responde.
- 2. Haz: La ruta de creación con validación completa.
- 3. Comprueba: Ninguna entrada inválida llega a tocar los datos.
Antes de empezar · 5 minutos, sin apuntes
- ¿Qué pasaría si alguien envía un campo
iden la creación? - ¿Y un campo que no existe en tu modelo?
- ¿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
- Nativa · comodidad
- Cliente · buena experiencia
- 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
- Implementa
POST /api/productoscon validación en tres pasos. - Devuelve 201,
Locationy el recurso creado. - Devuelve 400 con todos los errores, no solo el primero.
- Descarta los campos no permitidos y demuéstralo enviando uno de más.
- Intenta crear un producto saltándote el formulario y comprueba que la API se defiende.
- Comprueba qué ocurre si el cuerpo no es JSON válido.
Location.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.