La creación de una API RESTful es una habilidad fundamental para cualquier desarrollador web moderno. En este artículo, exploraremos cómo implementar la operación PUT en nuestro CRUD, permitiéndonos actualizar registros de usuarios de manera efectiva. Aprenderemos a manejar rutas dinámicas, procesar parámetros y actualizar datos en un archivo JSON, todo mientras identificamos posibles problemas y mejores prácticas.
¿Cómo implementar la operación PUT en una API RESTful?
Continuando con nuestro desarrollo CRUD, ahora nos enfocaremos en la operación PUT, que nos permite actualizar información de usuarios existentes. Esta operación es esencial en cualquier aplicación que maneje datos persistentes.
Para implementar correctamente un endpoint PUT, necesitamos:
Definir una ruta dinámica que acepte un identificador.
Recibir y procesar los datos actualizados.
Encontrar el registro específico a modificar.
Aplicar los cambios y guardar la información actualizada.
Comencemos definiendo nuestra ruta en la aplicación:
app.put('/users/:id',(req, res)=>{// Obtenemos el ID del usuario desde los parámetros de la rutaconst userId =parseInt(req.params.id,10);// Obtenemos los datos actualizados del cuerpo de la peticiónconst updateUser = req.body;// Lógica para actualizar el usuario// ...});
¿Cómo manejar la lectura y actualización de datos?
Una vez definida nuestra ruta, necesitamos implementar la lógica para leer el archivo de usuarios, encontrar el usuario específico, actualizarlo y guardar los cambios:
app.put('/users/:id',(req, res)=>{const userId =parseInt(req.params.id,10);const updateUser = req.body;// Leemos el archivo de usuarios fs.readFile(userFilePath,'utf8',(error, data)=>{if(error){return res.status(500).json({error:"Error con conexión de datos"});}// Parseamos los datos JSONlet users =JSON.parse(data);// Actualizamos el usuario que coincida con el ID users = users.map(user=>{return user.id=== userId ?{...user,...updateUser }: user;});// Guardamos los cambios en el archivo fs.writeFile(userFilePath,JSON.stringify(users,null,2),(err)=>{if(err){return res.status(500).json({error:"Error al actualizar el usuario"});}// Respondemos con el usuario actualizado res.json(updateUser);});});});
En este código:
Convertimos el ID de la URL a un número entero con parseInt().
Extraemos los datos actualizados del cuerpo de la petición.
Leemos el archivo de usuarios existente.
Utilizamos el método map() para recorrer todos los usuarios y actualizar solo el que coincida con el ID proporcionado.
Guardamos los cambios en el archivo.
Respondemos con el usuario actualizado.
¿Qué problemas pueden surgir al actualizar registros?
Durante la implementación de la operación PUT, es importante estar atento a posibles problemas:
Duplicidad de IDs: Si existen múltiples registros con el mismo ID en nuestra base de datos, todos serán actualizados cuando intentemos modificar uno específico. Esto puede causar inconsistencias graves en los datos.
Por ejemplo, si tenemos dos usuarios con ID=1 y actualizamos uno de ellos, ambos registros se modificarán:
// Antes de la actualización[{id:1,name:"Jane",email:"jane@example.com"},{id:2,name:"Bob",email:"bob@example.com"},{id:1,name:"Alice",email:"alice@example.com"}]// Después de actualizar el usuario con ID=1[{id:1,name:"John Smith",email:"john2@example.com"},{id:2,name:"Bob",email:"bob@example.com"},{id:1,name:"John Smith",email:"john2@example.com"}]
Falta de validaciones: Es crucial implementar validaciones para:
Verificar que el ID existe antes de intentar actualizar.
Validar que los datos enviados cumplen con el formato esperado.
Asegurar que no existan IDs duplicados en la base de datos.
¿Cómo probar nuestra implementación PUT?
Para probar nuestra implementación, podemos utilizar herramientas como Postman:
Crear una nueva solicitud PUT.
Establecer la URL con el ID del usuario a actualizar (por ejemplo, http://localhost:3000/users/1).
Configurar el cuerpo de la solicitud con los datos actualizados en formato JSON:
{"name":"John Smith","email":"john2@example.com"}
Enviar la solicitud y verificar la respuesta.
Comprobar que los cambios se hayan aplicado correctamente en nuestro archivo de usuarios.
Importante: Siempre verifica que los cambios se hayan aplicado correctamente y solo a los registros deseados.
La implementación de la operación PUT es fundamental para cualquier API RESTful completa. Con una correcta validación y manejo de errores, podemos asegurar que nuestros datos se mantengan consistentes y que las actualizaciones se apliquen de manera precisa. ¿Has implementado alguna vez una API con operaciones CRUD? Comparte tu experiencia en los comentarios.
aui mi solucion use findIndex para solo actualizar en el primer match y el campo id no deberia estar en el update```js
app.put("/users/:id", async (req, res) => {
try {
const { id } = req.params;
const { name, email } = req.body;
if(!name &&!email){return res
.status(400).json({error:"Debe enviar al menos uno de los campos: name o email",});}const data =await fsPromises.readFile(usersFilePath,"utf-8");const users = data.trim()===""?[]:JSON.parse(data);const userIndex = users.findIndex((user)=> user.id===Number(id));if(userIndex ===-1){return res.status(404).json({error:"Usuario no encontrado"});}// Actualizar solo los campos enviadosif(name) users[userIndex].name= name;if(email) users[userIndex].email= email;await fsPromises.writeFile(usersFilePath,JSON.stringify(users,null,2));res.json(users[userIndex]);
} catch (err) {
console.error("Error al actualizar el usuario:", err);
res.status(500).json({ error: "Error al leer o escribir el archivo" });
}
});
Excelente trabajo 👌 como aporte la validación de los campos de esta manera if (name) users[userIndex].name = name;
if (email) users[userIndex].email = email; cuando son muchos campos se vuelve más complicada, por lo que usar el spread operator facilita esta tarea, ya que sí tienen propiedades con el mismo nombre, la última sobrescribe a la anterior. { ...user, ...updatedUser } por lo que no es necesario validar dato por dato.
*//PUT cambiar un usuario con base en el archivo*app.put('/users/:id',(req, res)=>{ *//se leen los usuarios existentes*  fs.readFile(usersFilePath,'utf-8',(err, data)=>{ if(err){ return res.status(500).json({error:'Error en la conexion de datos'}); } const users =JSON.parse(data); const userId =parseInt(req.params.id); const indice = users.findIndex(u=> u.id=== userId) if(indice ===-1){ return res.status(500).json({error:'El userId no existe'}); } *//se valida que la informacion del requerimiento sea valida* const newUser = req.body;   if(!newUser ||Object.keys(newUser).length===0){ return res.status(400).json({error:'No se recibieron datos'}); } *//valida si el nombre tiene al menos dos partes* if(!validarNombre(newUser.name)){ return res.status(500).json({error:'Nombre invalido'}); } *//valida que la sintaxis del email este bien* if(!validarEmail(newUser.email)){ return res.status(500).json({error:'email invalido'}); } *//se reemplaza el usuario existente por el nuevo usuario y se guarda en el archivo*  users\[indice]= newUser;  fs.writeFile(usersFilePath,JSON.stringify(users,null,4),err=>{ if(err){ return res.status(500).json({error:'Error al guardar el usuario'}); } }); *//se envia el resultado que el usuario ha sido creado.*  res.status(200).json(newUser); }); });
Cree una función que valida si el ID es único, si el name existe y cumple entre un rango de 8 y 20 caracteres , y si el email cumple con la expresión regular. Si falla retorna un status 400 ,y un mensaje indicando el error, y si todo ok sigue en la lógica de la aplicación.
🗒 Nota: Para lograr que fuera una función reutilizable y usarla en el método put, le añadí una booleano llamado isEditing, de forma que si indicamos que estamos editando, no debe comprobar si ese ID es único en la base de dato, y no debería modificarse los IDs, al menos no manualmente por el usuario.
Acá el código 💚💻:
constvalidateForm=(userToCheck,users, isEditing)=>{if(!isEditing && users.some(user=> user.id=== userToCheck.id)){return{error:true,message:'ID ya existente en la base de datos'}}if(!userToCheck.name|| userToCheck.name.length<8|| userToCheck.name.length>20){return{error:true,message:'El nombre de usuario debe tener entre 8 y 20 caracteres'}}const regex =/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;if(!regex.test(userToCheck.email)){return{error:true,message:'El correo electronico no es valido'}}returntrue;}
Mi solución.
// CRUD - U of Update!app.put('/users/:id',(req, res)=>{const userId =parseInt(req.params.id,10)const updatedUser = req.body;// Validar que no se está intentando modificar el campo 'id'if('id'in updatedUser){return res.status(400).json({error:'No se puede modificar el campo id.'});}// Validar formato de datos enviadosif(updatedUser.name&& updatedUser.name.length<3){return res.status(400).json({error:'El nombre debe tener al menos 3 caracteres.'});}if(updatedUser.email){const emailRegex =/^[^\s@]+@[^\s@]+\.[^\s@]+$/;if(!emailRegex.test(updatedUser.email)){return res.status(400).json({error:'El email no es válido.'});}}// Leer el archivo de usuarios fs.readFile(usersFilePath,'utf-8',(err, data)=>{if(err){return res.status(500).json({error:'Error con conexión de datos.'});}let users =[];try{ users =JSON.parse(data);}catch(e){ users =[];}// Verificar que no existan IDs duplicadosconst idCount = users.filter(u=> u.id=== userId).length;if(idCount >1){return res.status(400).json({error:'Existen IDs duplicados en la base de datos.'});}// Verificar que el ID existeconst userIndex = users.findIndex(u=> u.id=== userId);if(userIndex ===-1){return res.status(404).json({error:'Usuario no encontrado.'});}// Actualizar solo el primer usuario encontrado users[userIndex]={...users[userIndex],...updatedUser }; fs.writeFile(usersFilePath,JSON.stringify(users,null,2),err=>{if(err){return res.status(500).json({error:"Error al actualizar el usuario"});} res.json(users[userIndex]);});});});
app.put('/users/:id',(req, res)=>{const userId =parseInt(req.params.id,10);const updatedUser = req.body;const{name, email}= updatedUser;const regex_email =/^[^\s@]+@[^\s@]+\.[^\s@]+$/;const regex_name =/^[a-zA-Z\s]{3,}$/;// Check if all required fields are presentif(!name){return res.status(400).json({error:'Missing required field: name'});}elseif(!email){return res.status(400).json({error:'Missing required field: email'});};//Data validationif(!updatedUser ||Object.keys(updatedUser).length===0){return res.status(400).json({error:'No user data provided'});}if(!name ||!regex_name.test(name)){return res.status(400).json({error:'User name must be at least 3 characters long and contain only letters'});}if(!email ||!regex_email.test(email)){return res.status(400).json({error:'Invalid email format'});} fs.readFile(usersFilePath,'utf-8',(err, data)=>{if(err){return res.status(500).json({error:'Error with data connection'});}let users =JSON.parse(data);let userFound =false; users = users.map(user=> user.id=== userId ?(userFound =true,{...user,...{name,email}}): user);if(!userFound){return res.status(404).json({error:'User not found'});} fs.writeFile(usersFilePath,JSON.stringify(users,null,2),(err)=>{if(err){return res.status(500).json({error:'Error saving user'});} res.json({name,email});});});});