Validaciones en POST y PUT para Datos de Usuario en APIs
Resumen
La validación de datos es un aspecto fundamental en el desarrollo de APIs RESTful para garantizar la integridad y consistencia de la información. Implementar validaciones adecuadas en los endpoints de POST y PUT evita errores que podrían comprometer toda la base de datos y asegura que la aplicación funcione correctamente. Estas prácticas no solo mejoran la calidad del código, sino que también proporcionan una mejor experiencia al usuario final.
¿Cómo implementar validaciones efectivas en APIs RESTful?
Cuando desarrollamos APIs, es crucial validar los datos que recibimos antes de procesarlos. En este caso, hemos utilizado la inteligencia artificial como herramienta para generar validaciones robustas para nuestros endpoints. La implementación de estas validaciones nos permite controlar la calidad de los datos que ingresan a nuestro sistema.
Las validaciones que hemos implementado incluyen:
Verificación de correos electrónicos mediante expresiones regulares
Comprobación de longitud mínima para nombres de usuario
Validación de que los IDs sean numéricos y únicos
Estas validaciones se han encapsulado en funciones reutilizables que pueden ser compartidas entre el backend y el frontend si fuera necesario.
¿Qué funciones de validación debemos implementar?
Las funciones de validación que hemos creado son:
// Validación de correo electrónico mediante RegexfunctionisValidEmail(email){const emailRegex =/^[^\s@]+@[^\s@]+\.[^\s@]+$/;return emailRegex.test(email);}// Validación de nombre (mínimo 3 caracteres)functionisValidName(name){returntypeof name ==='string'&& name.length>=3;}// Validación de ID (numérico y único)functionisValidId(id, users){const isNumeric =!isNaN(id);const isUnique =!users.some(user=> user.id=== id);return isNumeric && isUnique;}// Función principal de validaciónfunctionvalidateUser(user, users){const errors =[];if(!isValidName(user.name)){ errors.push("El nombre debe tener al menos tres caracteres");}if(!isValidEmail(user.email)){ errors.push("El correo electrónico no es válido");}if(!isValidId(user.id, users)){ errors.push("El ID debe de ser numérico y único");}return{isValid: errors.length===0,errors: errors
};}
Estas funciones se guardan en un archivo llamado validation.js dentro de la carpeta de utilidades, lo que permite importarlas fácilmente en cualquier parte de nuestra aplicación.
¿Cómo integrar las validaciones en los endpoints?
Para integrar estas validaciones en nuestros endpoints de POST y PUT, necesitamos importar la función principal validateUser y utilizarla antes de procesar los datos:
import{ validateUser }from'./utils/validation.js';// En el endpoint POSTapp.post('/users',(req, res)=>{const newUser = req.body;const validation =validateUser(newUser, users);if(!validation.isValid){return res.status(400).json({error: validation.errors});}// Continuar con la lógica para crear el usuario users.push(newUser); res.status(201).json(newUser);});// En el endpoint PUTapp.put('/users/:id',(req, res)=>{const updatedUser = req.body;const validation =validateUser(updatedUser, users);if(!validation.isValid){return res.status(400).json({error: validation.errors});}// Continuar con la lógica para actualizar el usuario// ...});
¿Cómo probar las validaciones implementadas?
Para verificar que nuestras validaciones funcionan correctamente, podemos utilizar herramientas como Postman para enviar diferentes tipos de solicitudes a nuestros endpoints:
Caso exitoso: Enviar datos válidos que cumplan con todas las reglas de validación.
ID duplicado: Intentar crear un usuario con un ID que ya existe.
Nombre corto: Enviar un nombre con menos de tres caracteres.
Correo inválido: Proporcionar un correo electrónico con formato incorrecto.
Al probar estos casos, deberíamos recibir respuestas apropiadas:
Para casos exitosos: código 201 (Created) y los datos del usuario.
Para casos con errores de validación: código 400 (Bad Request) y mensajes específicos sobre los errores encontrados.
¿Qué desafíos pueden surgir con estas validaciones?
Un desafío común es la validación en el endpoint PUT. Como se observó en las pruebas, puede haber un conflicto cuando intentamos actualizar un usuario existente, ya que la validación de ID único podría rechazar la operación incorrectamente.
Este es un reto interesante: modificar la función de validación para que permita actualizar un usuario existente sin que la validación de ID único interfiera. Una posible solución sería adaptar la función isValidId para que considere el contexto de la operación (creación vs. actualización).
La implementación de validaciones robustas es un paso fundamental para construir APIs confiables y seguras. Estas prácticas no solo previenen errores en los datos, sino que también mejoran la experiencia del usuario al proporcionar mensajes de error claros y específicos. ¿Has enfrentado desafíos similares en tus proyectos? Comparte tus experiencias y soluciones en los comentarios.
// la función de validación del usuario quedaria de la siguiente forma
function validateUser(user, users) {
console.log(user)
const errors = [];
let codeError =0;
if (!isValidName(user.name)) {
errors.push("El nombre debe tener al menos tres caracteres");
codeError = 1;
}
if (!isValidEmail(user.email)) {
errors.push("El correo electrónico no es válido");
codeError =2;
}
if (!isValidId(user.id, users)) {
errors.push("El ID debe de ser numérico y único");
codeError = codeError != 0 ? codeError : 3;
}
return {
isValid: errors.length === 0,
errors: errors,
codeError: codeError,
};
}
otro opcion para no sobre escribir la variable codeError seria guardalo en un array y con el metodo map recorrerlo para identificar si viene el codigo de erro 3 que es el que indica si el id ya se encuntra dentro del archivo JSON.
Espero les guste esta solución en mi opinión es una solución limpia y que no involucra hacer muchas cambios dentro del código.
Agradecería poder contar con sus opiniones.
Saludos compañeros;
Para darle solución al bug presentado por el profesor, lo que hice fue dividir la funciones de validación de ID en el archivo de validaciones. Adjunto archivo "validations.js":
// Validación de correo electrónico mediante RegexfunctionisValidEmail(email){// Expresión regular para validar el email.const emailRegex =/^[^\s@]+@[^\s@]+\.[^\s@]+$/;return emailRegex.test(email);}// Validación de nombre (mínimo 3 caracteres)functionisValidName(name){returntypeof name ==='string'&& name.length>=3;}// Validación de la no repeticion del numero de ID.functionisUniqueId(id, users){const isUnique = users.some(user=> user.id=== id);return isUnique
}// Validación de ID (numérico y único)functionisValidId(id){// Expresión regular para validar que el id es un entero positivo.const idRegex =/^[0-9]+$/;returntypeof id ==='number'&& idRegex.test(id)}// Función principal de validaciónfunctionvalidateUser(user, users){const errors =[];if(!isValidName(user.name)){ errors.push("El nombre debe tener al menos tres caracteres");}if(!isValidEmail(user.email)){ errors.push("El correo electrónico no es válido");}if(!isValidId(user.id)){ errors.push("El ID debe de ser un numero positivo");}return{isValid: errors.length===0,errors: errors
};}module.exports={ isValidEmail, isValidName, isUniqueId, isValidId, validateUser
};```Con esto dicha validación que se encarga de ver que no se repita la ID solo la incluyo en POST, pero no en el PUT.
```js
// Ruta para creacion de usuarios (API).app.post('/users',(req, res)=>{// Capturamos los datos implicitos en el cuerpo de la solicitud.const newUser = req.body;// Desarrollamos lectura de los datos existentes en el archivo JSON. fs.readFile(usersFilePath,'utf-8',(error, data)=>{// Manejo de erroresif(error){return res.status(500).json({error:"Error con conexión de datos"});}// Parseamos los datos JSON a Objeto.const users =JSON.parse(data);// Integramos las funciones de validacion.const validationId =isUniqueId(newUser.id, users)const validation =validateUser(newUser, users);// Validamos los datos recibidos.// --> Validamos que nombre exista y tenga al menos 3 caracteres.if(!validation.isValid){return res.status(400).json({error: validation.errors});}elseif(validationId){return res.status(400).json({error:"Esa ID ya esta registrada, ingrese una diferente"});}else{// Agregamos el nuevo usuario al formato JSON existente en el archivo. users.push(newUser);// Guardamos la información del JSON actualizada en el archivo JSON. fs.writeFile(usersFilePath,JSON.stringify(users,null,2),(error)=>{// Manejo de erroresif(error){return res.status(500).json({error:"Error al guardar el usuario nuevo"});}else{// Respuesta de creacion y agregado del nuevo usuario (Codigo de estado HTTP = 201 = CREATED).return res.status(201).json(newUser);}});}});});``````js
app.put('/users/:id',(req, res)=>{// Extraemos el id de la ruta dinamica y lo convertimos de string a int.const userId =parseInt(req.params.id,10);// Obtenemos los datos de la solicitud.const updateUser = req.body; fs.readFile(usersFilePath,'utf8',(error, data)=>{if(error){return res.status(500).json({error:"Error con conexión de datos"});}// Parseamos los datos JSON a Objeto.let users =JSON.parse(data);// Integramos las funciones de validacion.const validation =validateUser(updateUser, users);// Validamos los datos recibidos.if(!validation.isValid){return res.status(400).json({error: validation.errors});}elseif(updateUser.id!== userId){// Validamos que la ID de la solicitud y la ID del EndPoint sean igualesreturn res.status(400).json({error:"La ID en el EndPoint de la solicitud y la ID del formato JSON deben ser iguales"});}else{// Recorremos todos los usuarios y actualizar solo el que coincida con el ID proporcionado. users = users.map(user=>{// Actualizamos el usurio que coincida con el id.return user.id=== userId ?{...user,...updateUser }: user;});// Guardamos los cambios en el archivo JSON. fs.writeFile(usersFilePath,JSON.stringify(users,null,2),(error)=>{// Manejamos erroresif(error){return res.status(500).json({error:"Error al actualizar el usuario"});}else{ res.status(200).json(updateUser);}});}});});```Además que agregue un condicional para que las ID las solicitudes PUT tengan que coincidir con la ID en el cuerpo del EndPoint.
Ahí les va chatos:
Yo Elegi un Objecto
const userSchema ={name:{required:true,pattern:/^[a-zA-Z\s]{3,100}$/,},email:{required:true,pattern:/^[^\s@]+@[^\s@]+\.[^\s@]+$/,},};functioncapitalizer(field){return field.charAt(0).toUpperCase()+ field.slice(1);}functionvalidateSchema(data, schema){const errors =[];if(!data ||typeof data !=="object"){ errors.push("Request body is required");return errors;}for(const[field, rule]ofObject.entries(schema)){const value = data[field];const label =capitalizer(field);if(rule.required&&!value){ errors.push(`${label} is required`);continue;}if(rule.pattern&& value &&!rule.pattern.test(value)){ errors.push(`Invalid ${field} format`);}}return errors;}
RETO: Permitir que se permita actualizar modificando el filtro del ID
Para CREAR usuario: Filtro numérico y de no duplicidad
PARA ACTUALZIAR: Filtro numérico
Si no te quedó claro la función que valida el ID, mira esta lámina y verás que lo entiendes:
Así solucione el update. Lo hice sin modificar el validación, hice una verificación adicional, para que no actualice si el id de la api es diferente al id del usuario de la ruta. Como verán en mi código, solo hay que fijarse en la lógica booleana, solo modifique una parte del código quite el "!" y añadí una capa mas.
Fíjense donde esta seleccionado.
Actualice mi código, tenia errores, pero ya lo solucione.
Y si el email o nombre esta mal? y si se manda un id en el body diferente del id del url?
Mi aporte con la solución de ID único:
functionisValidEmail(email){const emailRegex =/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;return emailRegex.test(email);}functionisValidName(name){returntypeof name ==='string'&& name.length>=3;}functionisUniqueNumericId(id, users, userId){ userIdSome = users.some(user=> user.id=== id);returntypeof id ==='number'&&(!userIdSome ||(userIdSome && id === userId));}functionvalidateUser(user, users, userId){const{ name, email, id }= user;if(!name ){return{isValid:false,error:'Missing required field: name'};}if(!isValidName(name)){return{isValid:false,error:'User name must be at least 3 characters long and contain only letters'};}if(!email ){return{isValid:false,error:'Missing required field: email'};}if(!isValidEmail(email)){return{isValid:false,error:'Invalid email format'};}if( id &&!isUniqueNumericId(id, users, userId)){return{isValid:false,error:'User id must be unique'};}return{isValid:true};}module.exports={ isValidEmail, isValidName, isUniqueNumericId, validateUser
};
// validation.js/** hidden */constisUniqueId=(id, users)=>{return!users.some((user)=> user.id=== id);};constisNumericId=(id)=>{returntypeof id ==="number";};constvalidateUniqueUser=(user, users)=>{const{ id }= user;const validation =validateUser(user, users);if(!validation.isValid){return{isValid:false,error: validation.error};}if(!isUniqueId(id, users)){return{isValid:false,error:"El id debe ser único."};}return{isValid:true};};constvalidateUser=(user)=>{/** hidden */if(!isNumericId(id)){return{isValid:false,error:"El id debe ser numérico.",};}/** hidden */};/** hidden */// app.jsapp.post("/users",(req, res)=>{/** hidden */const uniqueUserValidation =validateUniqueUser(newUser, users);if(!uniqueUserValidation.isValid){return res.status(400).json({error: validation.error});}/** hidden */});app.put("/users/:id",(req, res)=>{/** hidden */const userValidation =validateUser(updatedUser, users);if(!userValidation.isValid){return res.status(400).json({error: validation.error});}/** hidden */});```Use la idea de dividir la validación de josé, y despues cree una validación para un usuario unico que usa la validacion del usuario y le agrega la validacion de que sea unico.
Aporte: Mi validación la hice con una función local y asegurándome de manejar un id único cuando se crea un usuario nuevo ( GUID ).
constvalidateUser=(user)=>{const{ name, email }= user;const regex =/^[^\s@]+@[^\s@]+\.[^\s@]+$/;if(!name ||typeof name !=='string'|| name?.length <3){console.log('Invalid user data:', user);thrownewError('Name must be a string with at least 3 characters');}if(!email ||!regex.test(email)){console.log('Invalid user data:', user);thrownewError('Invalid email format');}returntrue;}
Mi solución para el bug en el update fue usar las funciones de validación separaradas