DEV Community

Ramses Mata for AWS

Posted on

GraphQL en tiempo real: Subscriptions con AWS AppSync

En el artículo anterior exploramos las dos formas principales de mantener datos frescos en una aplicación: polling y subscriptions. Entendimos cómo funcionan, cuándo usar cada una, y las diferencias entre ambas. Ahora es momento de pasar de la teoría a la práctica.

En este artículo vamos a implementar subscriptions en nuestra API de GraphQL con AWS AppSync (el servicio administrado de GraphQL que hemos usado en la serie). Vamos a construir algo concreto: suscribirnos a una publicación y recibir una notificación en tiempo real cada vez que alguien deje un nuevo comentario.

Al final de este artículo, tendrás:

  • Una subscription funcionando que reacciona a nuevos comentarios
  • Entendimiento de cómo AppSync implementa subscriptions (spoiler: es más simple de lo que piensas)
  • La experiencia de ver datos aparecer en tiempo real en dos pestañas del navegador

Este diagrama te muestra el panorama completo de lo que construiremos:

Diagrama: panorama general - suscribirse a una publicación y recibir comentarios en tiempo real

Antes de empezar

Para seguir este tutorial necesitas:


Parte 1: ¿Cómo implementa AppSync las Subscriptions?

En el artículo anterior vimos que hay diferentes formas de implementar subscriptions (WebSockets, SSE, Long Polling). AppSync usa WebSockets por debajo, pero tiene una particularidad importante: las subscriptions se activan a través de mutations.

¿Qué significa esto? Que cuando defines una subscription en AppSync, le dices: "cada vez que se ejecute esta mutation, notifica a todos los que estén suscritos". No necesitas escribir código para enviar notificaciones, AppSync lo hace automáticamente.

El flujo se ve así:

  1. Un cliente se suscribe a onNuevoComentario(publicacionId: "1")
  2. AppSync abre un WebSocket y registra la subscription
  3. Otro cliente ejecuta la mutation crearComentario para la publicación "1"
  4. AppSync detecta que hay una subscription vinculada a esa mutation
  5. AppSync envía los datos del nuevo comentario al cliente suscrito automáticamente

Diagrama: flujo de subscriptions en AppSync - mutation dispara notificación via WebSocket

¿Por qué mutations? Porque las mutations son las operaciones que cambian datos en tu sistema. Si algo cambió, probablemente alguien quiere saberlo. AppSync aprovecha esto para vincular subscriptions directamente a los cambios.


Parte 2: Preparando el resolver de comentarios

Antes de implementar la subscription, necesitamos que la mutation crearComentario funcione. Si completaste el reto del artículo 4, ya la tienes lista y puedes saltar a la Parte 3.

Si no, no te preocupes, vamos a configurarla rápidamente. El proceso es el mismo patrón que aprendiste para usuario y crearUsuario:

  1. Crear función Lambda
  2. Crear data source en AppSync
  3. Adjuntar resolver con mapping templates

Código del resolver

Crea una nueva función Lambda con este código:

export const handler = async (event) => {
  const { contenido, publicacionId, autorId } = event.arguments.input;

  const nuevoComentario = {
    id: Date.now().toString(),
    contenido,
    publicacionId,
    autorId,
    creadoEn: new Date().toISOString()
  };

  console.log("Comentario creado:", nuevoComentario);

  return nuevoComentario;
};
Enter fullscreen mode Exit fullscreen mode

💡 Tip importante: Usamos Date.now().toString() para generar IDs porque es simple y funciona bien para este tutorial. En una aplicación en producción con múltiples usuarios simultáneos, querrás usar algo más robusto como UUID o dejar que tu base de datos genere los IDs automáticamente.

Mapping templates

Los mismos que usamos para los resolvers anteriores:

Request mapping template:

{
    "version": "2018-05-29",
    "operation": "Invoke",
    "payload": {
        "arguments": $util.toJson($context.arguments)
    }
}
Enter fullscreen mode Exit fullscreen mode

Response mapping template:

$util.toJson($context.result)
Enter fullscreen mode Exit fullscreen mode

Conecta esta Lambda a AppSync como data source y adjunta el resolver a crearComentario en el esquema. Reutiliza el rol AppSyncLambdaRole que creaste en el artículo 4.

Prueba que funcione con esta mutation en el playground:

mutation CrearComentario {
  crearComentario(input: {
    contenido: "¡Excelente publicación!"
    publicacionId: "1"
    autorId: "2"
  }) {
    id
    contenido
    publicacionId
    creadoEn
  }
}
Enter fullscreen mode Exit fullscreen mode

Si recibes los datos del comentario de vuelta, estás listo para la siguiente parte.


Parte 3: Implementando la Subscription

Ahora sí, lo que vinimos a hacer. Vamos a agregar una subscription a nuestro esquema para que cualquier cliente pueda suscribirse a los comentarios nuevos de una publicación.

Paso 1: Actualizar el esquema

Agrega este tipo al final de tu esquema en AppSync:

type Subscription {
  onNuevoComentario(publicacionId: ID!): Comentario
    @aws_subscribe(mutations: ["crearComentario"])
}
Enter fullscreen mode Exit fullscreen mode

También necesitas agregar el campo publicacionId al tipo Comentario para que AppSync pueda filtrar las notificaciones por publicación:

type Comentario {
  id: ID!
  contenido: String!
  autor: Usuario!
  publicacion: Publicacion!
  publicacionId: ID
  creadoEn: String!
}
Enter fullscreen mode Exit fullscreen mode

Aquí está el esquema completo actualizado para que lo copies:

type Usuario {
  id: ID!
  nombre: String!
  email: String!
  fotoPerfil: String
  publicaciones: [Publicacion!]!
  creadoEn: String!
}

type Publicacion {
  id: ID!
  contenido: String!
  autor: Usuario!
  comentarios: [Comentario!]!
  creadoEn: String!
}

type Comentario {
  id: ID!
  contenido: String!
  autor: Usuario!
  publicacion: Publicacion!
  publicacionId: ID
  creadoEn: String!
}

input CrearUsuarioInput {
  nombre: String!
  email: String!
  fotoPerfil: String
}

input CrearPublicacionInput {
  contenido: String!
  autorId: ID!
}

input CrearComentarioInput {
  contenido: String!
  publicacionId: ID!
  autorId: ID!
}

type Query {
  usuario(id: ID!): Usuario
  publicacion(id: ID!): Publicacion
  publicaciones: [Publicacion!]!
}

type Mutation {
  crearUsuario(input: CrearUsuarioInput!): Usuario!
  crearPublicacion(input: CrearPublicacionInput!): Publicacion!
  crearComentario(input: CrearComentarioInput!): Comentario!
}

type Subscription {
  onNuevoComentario(publicacionId: ID!): Comentario
    @aws_subscribe(mutations: ["crearComentario"])
}
Enter fullscreen mode Exit fullscreen mode

En este video puedes ver cómo agregar el tipo Subscription directamente en el editor de esquema de AppSync:

Veamos qué hace cada parte:

  • type Subscription: Define las subscriptions disponibles en tu API, igual que type Query define las consultas y type Mutation define las escrituras
  • onNuevoComentario(publicacionId: ID!): El nombre de la subscription. Recibe el ID de la publicación a la que quieres suscribirte
  • : Comentario: El tipo de dato que recibirás cuando se active la subscription
  • @aws_subscribe(mutations: ["crearComentario"]): Esta es la parte clave. Le dice a AppSync: "activa esta subscription cada vez que se ejecute la mutation crearComentario"

💡 Tip importante: La directiva @aws_subscribe es específica de AppSync. Es lo que conecta una subscription con una o más mutations. Puedes vincular una subscription a múltiples mutations si lo necesitas: @aws_subscribe(mutations: ["crearComentario", "editarComentario"]).

Paso 2: Crear un Data Source de tipo NONE

Las subscriptions no llaman a Lambda ni a ninguna base de datos, solo reenvían datos. Pero AppSync necesita un data source asociado. Para esto usamos un data source de tipo NONE, que le dice a AppSync "no llames a nadie, solo pasa los datos".

En la consola de AppSync, ve a Data Sources y crea uno nuevo:

  • Nombre: NoneDataSource
  • Tipo: None

Paso 3: Adjuntar el resolver a la subscription

Ahora adjunta un resolver al campo onNuevoComentario en el tipo Subscription:

  • Data source: NoneDataSource (el que acabas de crear)
  • Runtime: VTL

Request mapping template:

{
    "version": "2018-05-29",
    "payload": {}
}
Enter fullscreen mode Exit fullscreen mode

Response mapping template:

null
Enter fullscreen mode Exit fullscreen mode

💡 Tip importante: Fíjate que el request template usa payload: {} y el response retorna null. Esto es porque al suscribirte no hay datos que devolver todavía, solo estás registrando tu interés. Los datos reales llegarán cuando alguien ejecute la mutation crearComentario.

¿Por qué no necesita Lambda? Porque la subscription no "busca" datos, solo escucha. Los datos vienen de la mutation (crearComentario), que ya tiene su propio resolver. La subscription simplemente reenvía esos datos a los clientes suscritos.


Parte 4: Probando en tiempo real

Este es el momento más satisfactorio. Vamos a abrir dos pestañas del navegador y ver la magia del tiempo real.

Diagrama: setup de prueba con dos pestañas - Tab A suscrito, Tab B ejecuta mutation

Pestaña A: El suscriptor

En la primera pestaña del playground de AppSync, ejecuta esta subscription:

subscription EscucharComentarios {
  onNuevoComentario(publicacionId: "1") {
    id
    contenido
    publicacionId
    creadoEn
  }
}
Enter fullscreen mode Exit fullscreen mode

Cuando la ejecutes, verás que el playground se queda "escuchando". No hay respuesta todavía, está esperando que alguien cree un comentario en la publicación "1".

💡 Tip importante: Los campos que pides en la subscription deben ser un subconjunto de los campos que pides en la mutation. Si la subscription pide un campo que la mutation no incluyó, recibirás null en ese campo (o un error si el campo es obligatorio en el esquema).

Pestaña B: El que comenta

Abre una segunda pestaña del playground de AppSync y ejecuta esta mutation:

mutation CrearComentario {
  crearComentario(input: {
    contenido: "¡Este artículo me ayudó mucho!"
    publicacionId: "1"
    autorId: "2"
  }) {
    id
    contenido
    publicacionId
    creadoEn
  }
}
Enter fullscreen mode Exit fullscreen mode

💡 Tip importante: Fíjate que la mutation pide publicacionId en los campos de respuesta. Esto es clave: AppSync usa los campos de la respuesta de la mutation para enviar datos a los suscriptores. Si no incluyes publicacionId, AppSync envía null en ese campo. Como tu subscription filtró por publicacionId: "1", no hay match con null y no recibes la notificación.

Este diagrama muestra cómo AppSync decide a quién notificar:

Diagrama: cómo AppSync filtra subscriptions usando publicacionId

El momento "aha" 🎉

Vuelve a la Pestaña A. Sin haber tocado nada, los datos del nuevo comentario aparecieron automáticamente. El servidor le avisó a tu suscriptor en el instante que la mutation se ejecutó.

En este video puedes ver la experiencia completa, suscribirse en una pestaña, crear un comentario en otra, y ver los datos aparecer en tiempo real:

Prueba crear más comentarios desde la Pestaña B, cada uno aparecerá en la Pestaña A en tiempo real.

💡 Tip importante: Fíjate que si creas un comentario con publicacionId: "2", la Pestaña A no recibe nada. Esto es porque se suscribió específicamente a la publicación "1". El filtro por publicacionId funciona automáticamente gracias a que lo definimos como argumento en la subscription.


Parte 5: Reto práctico

Ya implementaste tu primera subscription. Ahora es tu turno de expandir lo que aprendiste.

Tu reto: Implementa estas subscriptions adicionales:

  1. onNuevaPublicacion: Suscríbete para recibir notificaciones cuando alguien cree una nueva publicación. Vincula esta subscription a la mutation crearPublicacion.

  2. onNuevoUsuario: Suscríbete para saber cuando se registra un nuevo usuario. Vincula a crearUsuario.

Tips:

  • El patrón es el mismo: agrega el tipo en Subscription, usa @aws_subscribe, crea un resolver con el NoneDataSource, y prueba con dos pestañas
  • No necesitas crear nuevos resolvers de Lambda, las subscriptions usan las mutations existentes
  • Puedes reutilizar el mismo NoneDataSource para todas tus subscriptions
  • Experimenta con los campos que pides en la subscription, puedes pedir solo los que te interesen

Preguntas frecuentes

¿Las subscriptions de AppSync funcionan fuera del playground?

Sí. El playground es solo para probar. En una aplicación real, usarías librerías como AWS Amplify que manejan la conexión WebSocket automáticamente desde tu frontend (React, Vue, Angular, etc.).

¿Qué pasa si se pierde la conexión?

AppSync mantiene la conexión enviando mensajes de keep-alive periódicamente. Si la conexión se interrumpe, es responsabilidad del cliente reconectarse y volver a registrar sus subscriptions. En producción, librerías como AWS Amplify manejan esta lógica de reconexión automáticamente por ti.

¿Puedo filtrar qué datos recibo en una subscription?

Sí. Como vimos con publicacionId, los argumentos de la subscription actúan como filtros. Solo recibes notificaciones que coincidan con los argumentos que especificaste.


Conclusión

Recapitulemos lo que lograste:

  • ✅ Descubriste cómo AppSync vincula subscriptions a mutations con @aws_subscribe
  • ✅ Aprendiste a configurar un data source NONE y un resolver para subscriptions
  • ✅ Implementaste una subscription real que reacciona a nuevos comentarios
  • ✅ Viste datos aparecer en tiempo real entre dos pestañas del navegador

Hasta ahora en la serie hemos construido una API de GraphQL completa: desde entender qué es GraphQL, diseñar un esquema, desplegarlo en AppSync, implementar resolvers, y ahora agregar tiempo real con subscriptions. Pero todo lo hemos probado desde el playground de AppSync.

En el próximo artículo vamos a dar el siguiente paso: consumir nuestra API desde una aplicación cliente real. Vamos a ver cómo conectar un frontend a nuestra API y usar queries, mutations y subscriptions desde código.

¡Nos vemos ahí! 🚀

Top comments (0)