Construir una operación de actualización en una arquitectura serverless requiere combinar la lógica de un GET (obtener el ID del registro) con la de un POST (enviar un body con datos nuevos). Aquí se explica paso a paso cómo lograrlo usando AWS Lambda, DynamoDB y Serverless Framework, completando así la tercera pieza del CRUD (acrónimo de Create, Read, Update, Delete) que toda aplicación necesita.
¿Cómo preparar la estructura de carpetas y el serverless.yml para el update?
El punto de partida es la función get users que ya existía en el proyecto. A partir de ella se crea una nueva carpeta llamada update-users con su propio handler [01:05]. Dentro del archivo serverless.yml se registra esta tercera función Lambda indicando:
El nombre de la función: updateUsers.
El handler apuntando a la nueva carpeta y archivo.
El método HTTP: en este caso se utiliza PATCH [01:52].
El método PATCH es la práctica recomendada por la documentación de DynamoDB y otros frameworks cuando se trata de actualizaciones parciales de un registro, a diferencia de PUT, que reemplaza el recurso completo. También es necesario agregar el parámetro /{id} en la ruta del endpoint, tal como se definió para el get user, ya que sin él la API Gateway devuelve un error 403 [07:42].
¿Qué sintaxis necesita DynamoDB para actualizar un registro?
DynamoDB utiliza una estructura de parámetros específica que puede parecer compleja al principio, pero ofrece gran flexibilidad en casos de uso avanzados [03:30]. El objeto params que se envía al método update incluye:
TableName: el nombre de la tabla, por ejemplo usersTable.
Key: un objeto JSON con el primary key del registro, en este caso el userId obtenido desde la URL con event.pathParameters.
UpdateExpression: un string que indica qué atributos modificar, usando la sintaxis SET #name = :name [04:05].
ExpressionAttributeNames: mapea los alias definidos en la expresión (como #name) al nombre real del atributo en la tabla.
ExpressionAttributeValues: asigna los valores concretos a los marcadores (como :name), tomándolos del body de la petición con body.name [06:10].
ReturnValues: se configura como "ALL_NEW" para que DynamoDB devuelva el registro completo ya actualizado [05:35].
Un detalle importante es el JSON.parse aplicado al event.body [03:05]. El body que llega al Lambda viene como string crudo, por lo que es necesario parsearlo antes de acceder a sus propiedades.
¿Cómo se reemplaza el query por un update en el código?
En la línea donde antes se ejecutaba un query de DynamoDB, ahora se invoca el método update pasándole el objeto params completo [05:50]. La respuesta del update incluye los atributos modificados, lo que permite confirmar al cliente exactamente qué cambió.
¿Qué pasa si el body no se conecta con los params?
Durante la construcción del código se cometió un error real: el body se definió pero no se utilizó dentro de ExpressionAttributeValues. El valor del nombre quedaba estático en lugar de tomar body.name [06:10]. Este tipo de descuido es frecuente y sirve como recordatorio de que cada variable declarada debe tener una referencia activa en la lógica.
¿Cómo desplegar y probar el update con Postman?
El despliegue se realiza con el comando serverless deploy [06:40], que empaqueta el código en un archivo .zip, lo sube a AWS y actualiza el stack de CloudFormation. Este stack orquesta automáticamente la creación de todos los recursos necesarios: la función Lambda, los permisos y el endpoint en API Gateway [08:30].
Para probar en Postman [07:05]:
Se selecciona el método PATCH.
Se agrega el ID del usuario al final de la URL.
Se envía un body en formato JSON con el nuevo valor, por ejemplo {"name": "Georgeshagi profe Serverless"}.
La respuesta confirma la actualización en milisegundos y retorna el objeto modificado [09:25].
Es posible que exista un pequeño delay entre el despliegue y la disponibilidad completa del endpoint; tras un par de minutos todo funciona con normalidad.
Si llegaste hasta aquí, comparte en los comentarios cómo adaptarías la lógica para actualizar otros campos como el teléfono en lugar del nombre, y muestra una captura de los registros almacenados en tu tabla DynamoDB.
Con ayuda de ChatGPT creé una función que genera un objeto params para el update, tal que sea posible actualizar una cantidad arbitraria de propiedades. Este fue el resultado.
functionmakeUpdateParams(tableName, key, attributesToUpdate){constExpressionAttributeNames={};constExpressionAttributeValues={};letUpdateExpression="SET ";// For each attribute to update, add it to the UpdateExpression and build the ExpressionAttributeNames and ExpressionAttributeValues objectsObject.entries(attributesToUpdate).forEach(([attributeName, attributeValue])=>{const keyName =`#${attributeName}`;const valueName =`:${attributeName}`;UpdateExpression+=`${keyName} = ${valueName}, `;ExpressionAttributeNames[keyName]= attributeName;ExpressionAttributeValues[valueName]= attributeValue;});// Remove the trailing comma and space from the UpdateExpressionUpdateExpression=UpdateExpression.slice(0,-2);// Build the params object with the tableName, key, UpdateExpression, and ExpressionAttributeNames and ExpressionAttributeValues objectsconst params ={TableName: tableName,Key:{pk: key },UpdateExpression,ExpressionAttributeNames,ExpressionAttributeValues,ReturnValues:"ALL_NEW",};return params;}
Consejos
En mi caso, antes de hacer el sls deploy me gusta hacer todas las pruebas con sls offline server para corregir todo en local en caso de haber fallas y una vez validado si mandar todo a la nube. Así evitamos constantemente hacer actualizaciones al stack de CloudFormation (lo cual resulta un poco lento y genera pequeños costos innecesarios).
Por otro lado, les recomiendo mucho a los que usan Insomnia (creo que debe haber una manera en Postman) crear estos entornos, para siempre hacer llamados a las mismas direcciones y solo cambiando el URL de la API de acuerdo a desarrollo o producción:
Así va mi DynamoDB por ahora:
Mi GitHub
Excelente recomendaciones! :rocket:
Gracias por compartir. Quiero agregar que es posible configurar en Postman colecciones de variables con diferente scopes (global, proyecto, endpoint) de acuerdo al scope que buscamos.
hola si buenas, mira que esta clase me ha costado dos horas de m itiempo por un gran cantidad de incomvenientes con esa lib de uuid. por la migracion de Node22.x y el gran lio entee CommondJS vs ESM . pues ahora ne { { 2025} } , uuid esta escrtio en ESM y no admite CJS tal como esta mi handler.js donde metido todas las cuatro metodos CRUD .
La razón directa del fallofues que la versión más reciente del paquete uuid ( npm install uuid) decidió que su punto de entrada principal solo se publicaría en el formato ES Modules (ESM). no compatible CommonJS, ES Modules, require(), import(), y El Problema Principal: uuid se Volvió ESM Only, Mi Archivo (Handler.js): Es CommonJS (CJS) (usa module.exports y require()).
La Librería (uuid): Es ES Modules (ESM).
El Conflicto: Cuando el código CJS intenta cargar la librería con require('uuid'), Node.js ve que la librería solo ofrece una interfaz ESM y dice: "No puedo usar require para cargar un módulo ESM" (de ahí el error ERR_REQUIRE_ESM).
la version actual de uuid Version 13.0.0
hacer el
import{ v4 as uuidv4 }from'uuid';uuidv4();
no me funciono y rompio mi codigo handler que esta en CJS.
Yo itnentado const { v4: uuid } = require('uuid'); que intenta cargar la librería uuid (que solo ofrece el formato ESM), Node.js en Lambda dice: "No puedo usar require() para importar un módulo ESM".
Por tnato la solucion final fue a la importación dinámica (await import('uuid')) te permite cargar módulos ESM dentro de tu código CJS (Handler.js). Como tus funciones son async, podemos usarlo de forma segura dentro de la función .
createUser. asi :
Eliminado cualquier línea de require('uuid') o import estático de uuid que esté en la parte superior del archivo.
Reemplazado completamente el código de mi función createUser con esta versión, que usa la importación dinámica await import('uuid') dentro del try block:// handler.js (Fragmento de la función createUser CORREGIDA)
Yo tenia este Error: Phase: invoke Status: errorError Type: Runtime.Unknown
"errorType": "Error", "errorMessage": "require() of ES Module /var/task/node_modules/uuid/dist-node/index.js from /var/task/Handler.js not supported.\nInstead change the require of index.js in /var/task/Handler.js to a dynamic import() which is available in all CommonJS modules.", "code": "ERR_REQUIRE_ESM", "stack": [ "Error [ERR_REQUIRE_ESM]: require() of ES Module /var/task/node_modules/uuid/dist-node/index.js from /var/task/Handler.js not supported.", "Instead change the require of index.js in /var/task/Handler.js to a dynamic import() which is available in all CommonJS modules.", " at TracingChannel.traceSync (node:diagnostics_channel:322:14)", " at Object.<anonymous> (/var/task/Handler.js:4:22)" ]}
y antes de eso , Yo tuve otros errores mas, respecto a el cambio de versiones en serverless, Yo lo solucionado asi.
// handler.js (Fragmento de la función createUser CORREGIDA)// Función para crear un usuario (POST)module.exports.createUser=async(event)=>{const data =JSON.parse(event.body);const{ name, email }= data;// Asumiendo que tu esquema tiene name, emailtry{// FIX CLAVE: Importación dinámica para cargar el módulo ESM 'uuid'const{v4: uuid }=awaitimport('uuid');// Generamos el IDconst id =uuid();const params ={TableName:USERS_TABLE,Item:{ id, name, email,createdAt:newDate().toISOString()},};await dynamodb.put(params).promise();return{statusCode:201,body:JSON.stringify(params.Item),};}catch(error){console.error(error);return{statusCode:500,body:JSON.stringify({error:'Could not create user',details: error.message}),};}};
Ahora pude funcionar, todos mis cuatro funcioens, getUsers, createUsers, updateUsers, deleteUsers, de CRUD en : GET, CREATE, UDPDATE, DELETE ya me funcionan.
Yo he podido ingresar varios datos desde postman a mis tabla de dynamoDB.
Yo no me quejo del curso, pero si seria bueno que l oactualizaran .
PD: Gemini me ayudo con el Debugging .
gracias por el aporte igualmente me ha costado un monton de trabas seguir pero creo que es parte de ser dev rompiendo se aprende.
Así va quedando mi tabla :-)
note que si le paso un id que no existe en la db la crea y para evitar esto agregue en los params
ConditionExpression: "attribute_exists(pk)"