En el artículo anterior, desplegamos nuestro esquema en AWS AppSync. Creamos una API de GraphQL completa con tipos, queries y mutations perfectamente definidos. Incluso exploramos la documentación que AppSync generó automáticamente, pero hay un problema.
Si intentas hacer una query en el playground de AppSync ahora mismo, no obtendrás datos. Tu API tiene un esquema bastante cool, aunque hay un detalle; es como un restaurante con un menú increíble pero sin cocina. El menú promete platillos deliciosos, pero nadie sabe cómo prepararlos.
Hoy vamos a construir esa cocina. Vamos a implementar resolvers - las funciones que realmente responden a tus queries y mutations con datos reales.
Al final de este artículo, tendrás:
- ✅ Un resolver funcionando para consultar usuarios por ID
- ✅ Un resolver funcionando para crear nuevos usuarios
- ✅ Entendimiento completo del patrón para implementar cualquier resolver
Y lo mejor: haremos todo en la consola de AWS. Sin configuración local, sin instalar nada, sin complicaciones.
Si eres más visual, este diagrama te puede ayudar a entender el panorama completo de lo que construiremos:
Antes de empezar
Para seguir este tutorial necesitas:
- Tu API de AppSync del artículo anterior
- Una cuenta de AWS (el free tier es suficiente)
- Un navegador web
Parte 1: Entendiendo los Resolvers
Antes de escribir código, entendamos qué son los resolvers y por qué los necesitamos.
¿Qué es un resolver?
Un resolver es una función que "resuelve" (responde) una query o mutation de GraphQL.
Piénsalo así:
- Tu esquema es el menú del restaurante (qué platillos ofreces)
- Los resolvers son las recetas (cómo preparar cada platillo)
- La fuente de datos son los ingredientes (de dónde vienen los datos)
Cuando un cliente hace una query como usuario(id: "123"), GraphQL necesita saber:
- ¿Esta query existe en mi esquema? ✅ (validación)
- ¿Cómo obtengo los datos para responderla? ← Aquí entra el resolver
¿Por qué AWS Lambda?
Vamos a usar AWS Lambda para escribir nuestros resolvers por estas razones:
- Serverless - No tienes que manejar servidores, actualizaciones, o escalamiento
- Pay-per-use - Solo pagas por el tiempo que tu código se ejecuta. El free tier incluye $100 USD en créditos que puedes usar en Lambda y otros servicios. Ana Cunha escribió una guía completa sobre cómo funciona el nuevo Free Tier si quieres entender más sobre los créditos
- Integración directa - Lambda se conecta perfectamente con AppSync
- Perfecto para aprender - Te enfocas en la lógica de tu resolver, no en infraestructura
Queries vs Mutations
Hoy implementaremos dos tipos de resolvers:
-
Query resolver (
usuario(id)) - Para leer datos. No cambia nada en tu sistema. -
Mutation resolver (
crearUsuario) - Para escribir datos. Cada ejecución crea algo nuevo.
💡 Tip importante: Usaremos datos mock (hardcodeados) en lugar de una base de datos real. Esto nos permite enfocarnos en entender cómo funcionan los resolvers sin la complejidad de configurar DynamoDB. El patrón que aprendas hoy funciona igual con una base de datos real - solo cambias de dónde vienen los datos.
Parte 2: Creando tu primer Lambda Function (Query Resolver)
Vamos a crear el resolver para la query usuario(id). Este resolver recibirá un ID y devolverá los datos de ese usuario.
En este video, creamos la función Lambda desde cero en la consola de AWS, escribimos el código del resolver y lo desplegamos:
Aquí está el código del resolver para que lo copies:
// Datos mock - en una aplicación real, estos vendrían de una base de datos
const usuarios = [
{
id: "1",
nombre: "Ana García",
email: "ana@example.com",
fotoPerfil: "https://example.com/ana.jpg",
creadoEn: "2024-01-15T10:30:00Z"
},
{
id: "2",
nombre: "Carlos López",
email: "carlos@example.com",
fotoPerfil: "https://example.com/carlos.jpg",
creadoEn: "2024-02-20T14:20:00Z"
}
];
export const handler = async (event) => {
// AppSync envía los argumentos de la query en event.arguments
const { id } = event.arguments;
// Buscamos el usuario por ID
const usuario = usuarios.find(u => u.id === id);
// Retornamos el usuario encontrado, o null si no existe
return usuario || null;
};
Entendiendo el código
Los datos mock: Creamos un array con dos usuarios. En producción, esto vendría de DynamoDB, RDS, o cualquier base de datos.
export const handler: Esta es la función que Lambda ejecuta cuando se invoca. El async significa que puede hacer operaciones asíncronas (como llamar a una base de datos).
event.arguments: AppSync empaqueta los argumentos de tu query (en este caso, el id) en event.arguments. Así es como tu resolver sabe qué usuario está pidiendo el cliente.
return usuario || null: Si encontramos el usuario, lo retornamos. Si no, retornamos null. En GraphQL, retornar null para datos que no existen es normal y esperado.
💡 Tip importante: Cuando alguien hace la query
usuario(id: "1"), AppSync automáticamente toma eseid: "1"y lo pone enevent.arguments.id. No tienes que hacer nada especial - AppSync lo hace por ti.
Parte 3: Conectando Lambda a AppSync
Ahora que tenemos nuestra función Lambda lista, necesitamos conectarla a AppSync. Para esto necesitamos tres cosas:
- Un rol de IAM - para darle permiso a AppSync de invocar Lambda
- Un Data Source - para conectar AppSync con Lambda
- Un Resolver - para decirle a AppSync "cuando alguien pida usuario, usa este data source"
Creando el rol de IAM
AWS usa roles para controlar qué servicios pueden hacer qué. Necesitamos crear un rol que le dé permiso a AppSync para invocar nuestras funciones Lambda.
En este video, creamos el rol de IAM y le adjuntamos los permisos necesarios:
💡 ¿Por qué necesitamos un rol? Los servicios de AWS no pueden simplemente llamarse entre sí - necesitan permiso explícito. Este rol le da a AppSync permiso para invocar tus funciones Lambda. Lo bueno es que puedes reutilizar este mismo rol para todas tus funciones Lambda de GraphQL.
Creando el Data Source
Un data source es la forma en que AppSync se conecta a donde viven tus datos. En nuestro caso, es Lambda.
En este video, agregamos nuestra función Lambda como data source en AppSync:
💡 ¿Qué es un Data Source? Un data source puede ser Lambda, DynamoDB, un endpoint HTTP, o incluso otra API de GraphQL. AppSync usa el data source para saber a dónde ir a buscar los datos cuando recibe una query.
Adjuntando el Resolver a la Query
Ahora le decimos a AppSync que cuando alguien haga la query usuario(id), use nuestro data source de Lambda.
En este video, adjuntamos el resolver a la query usuario en el esquema. Usamos VTL (Velocity Template Language) como runtime y configuramos los mapping templates:
Aquí están los mapping templates para que los copies:
Request mapping template:
{
"version": "2018-05-29",
"operation": "Invoke",
"payload": {
"arguments": $util.toJson($context.arguments)
}
}
Response mapping template:
$util.toJson($context.result)
💡 ¿Qué son los mapping templates? Son plantillas que transforman datos entre AppSync y tu data source. El request template empaqueta los argumentos de la query (como el
id) en un formato que Lambda entiende. El response template toma lo que Lambda retorna y lo formatea para GraphQL.
💡 ¿Unit resolver vs Pipeline resolver? Un unit resolver trabaja con una sola fuente de datos (nuestro caso). Un pipeline resolver puede combinar múltiples fuentes de datos en secuencia. Para este tutorial, unit resolver es perfecto.
Parte 4: Probando tu Primer Resolver
¡Es momento de ver tu resolver en acción! Vamos a hacer una query real y ver datos reales de vuelta.
Copia esta query en el playground de AppSync:
query GetUsuario {
usuario(id: "1") {
id
nombre
email
fotoPerfil
creadoEn
}
}
En este video, ejecutamos la query y experimentamos con diferentes IDs y campos:
Fíjate en algo interesante: cuando pides un usuario que no existe (como id: "999"), recibes null en lugar de un error.
💡 ¿Por qué null y no un error? En GraphQL, retornar
nullpara datos que no existen es normal y esperado. Los errores son para cuando algo sale MAL (como si Lambda fallara o no tuviera permisos), no para "dato no encontrado". Esta es una distinción importante.
Parte 5: Segundo Resolver - Mutation (crearUsuario)
Ya sabes cómo crear un resolver para leer datos. Ahora vamos a crear uno para escribir datos. El proceso es el mismo patrón que ya aprendiste:
- Crear función Lambda
- Crear data source en AppSync
- Adjuntar resolver con mapping templates
- Probar
Aquí está el código del resolver para la mutation:
// En una aplicación real, esto guardaría en una base de datos
export const handler = async (event) => {
// Las mutations envían los datos en event.arguments.input
const { nombre, email, fotoPerfil } = event.arguments.input;
// Generamos un ID simple (en producción, usarías UUID o un ID de base de datos)
const id = Date.now().toString();
// Creamos el objeto del nuevo usuario
const nuevoUsuario = {
id,
nombre,
email,
fotoPerfil: fotoPerfil || null,
creadoEn: new Date().toISOString()
};
// En una aplicación real, aquí guardarías en la base de datos
console.log("Usuario creado:", nuevoUsuario);
// Retornamos el usuario creado
return nuevoUsuario;
};
Los mapping templates son los mismos que usamos para la query:
Request mapping template:
{
"version": "2018-05-29",
"operation": "Invoke",
"payload": {
"arguments": $util.toJson($context.arguments)
}
}
Response mapping template:
$util.toJson($context.result)
Y esta es la mutation para probar en el playground:
mutation CrearUsuario {
crearUsuario(input: {
nombre: "María Rodríguez"
email: "maria@example.com"
fotoPerfil: "https://example.com/maria.jpg"
}) {
id
nombre
email
creadoEn
}
}
En este video, creamos la función Lambda, la conectamos a AppSync (reutilizando el rol de IAM) y probamos la mutation:
💡 ¿Por qué event.arguments.input? Las mutations usan Input Types para agrupar todos los datos que se envían. En lugar de
event.arguments.nombre,event.arguments.email, etc., todo viene agrupado enevent.arguments.input. Esto mantiene las mutations limpias y organizadas.💡 ¿Por qué los datos no se guardan? Recuerda que estamos usando datos mock para aprender. Cada invocación de Lambda es independiente - no hay base de datos guardando los usuarios. En futuros artículos podríamos agregar DynamoDB para hacerlo persistente.
Parte 6: Reto Práctico
Ya conoces el patrón completo para crear resolvers. Ahora es tu turno de practicar.
Tu reto: Implementa los resolvers restantes usando el mismo patrón que aprendiste:
Resolvers de query:
-
publicacion(id: ID!): Publicacion- Crea datos mock con 2-3 publicaciones, busca por ID -
publicaciones: [Publicacion!]!- Retorna todas las publicaciones (no necesita argumentos)
Resolver de mutation:
-
crearPublicacion(input: CrearPublicacionInput!): Publicacion- Extrae titulo, contenido, autorId del input, genera ID y creadoEn
Tips para completar el reto:
- Sigue exactamente el mismo patrón: Lambda → Data Source → Resolver → Test
- Empieza con
publicaciones(es la más simple, no tiene argumentos) - Reutiliza el rol
AppSyncLambdaRolepara todos los data sources - Los mapping templates son los mismos para todos los resolvers
- Prueba cada resolver en el playground antes de pasar al siguiente
💡 ¿Te atoras? Es normal. Revisa los pasos que seguimos para
usuarioycrearUsuario. El patrón es idéntico, solo cambian los nombres de los campos y la estructura de los datos.
Conclusión
Lo que lograste hoy:
- ✅ Creaste tu primera función Lambda para un resolver de GraphQL
- ✅ Conectaste Lambda a AppSync como data source
- ✅ Implementaste un resolver de query (
usuario) - ✅ Implementaste un resolver de mutation (
crearUsuario) - ✅ Probaste ambos en el playground de GraphQL
- ✅ Aprendiste un patrón que puedes repetir para cualquier resolver
La idea clave: Los resolvers son simplemente funciones que retornan datos en la forma que tu esquema define. Ya sea que uses datos mock, una base de datos, o una API externa, el patrón es el mismo.
¿Qué sigue? Tu API funciona, pero ¿qué pasa cuando los datos cambian? ¿Tu app debería preguntar por actualizaciones cada cierto tiempo (polling)? ¿O el servidor debería enviar actualizaciones automáticamente (subscriptions)? En el próximo artículo exploraremos ambos enfoques y te ayudaremos a elegir el correcto para tu caso de uso.






