Mutaciones y suscripciones en GraphQL
En este artículo, veremos cómo usar los tipos Mutation
y Subscription
para manipular y observar los datos en busca de cambios, en lugar de solo realizar consultas, en GraphQL. No dudes en descubrir más en la documentación oficial.
Para simplificar las cosas, no utilizaremos ninguna base de datos ni solicitudes HTTP, pero es necesario saber cómo configurar una API básica con esquemas y resolutores.
Instalación
Usaremos la biblioteca graphql-yoga para configurar nuestro servidor y nodemon
hacer que se recargue automáticamente. También necesitaremos un preprocesador como Prepros o Babel para poder usar las últimas características de JavaScript.
$ npm i graphql-yoga nodemon
Configuración estándar
Además de nuestra configuración de servidor, solo tenemos una users
matriz vacía y un esquema y solucionador simples para devolver todos nuestros usuarios.
servidor.js
import { GraphQLServer } from 'graphql-yoga'const users = [];const typeDefs = ` type Query { users: [User!]! } type User { name: String! age: Int! }`;const resolvers = { Query: { user() { return users; } }}const server = new GraphQLServer({ typeDefs, resolvers });server.start(() = console.log('server running'));
Necesitaremos un start
script que ejecute nodemon en nuestro archivo de salida:
paquete.json
{ "name": "graphql-api", "version": "1.0.0", "description": "", "main": "server.js", "dependencies": { "graphql-yoga": "^1.16.7" }, "devDependencies": { "nodemon": "^1.19.1" }, "scripts": { "start": "nodemon server-dist.js" }, "author": "", "license": "ISC"}
Ahora en la terminal puedes simplemente ejecutar npm run start
.
localhost:4000
Deberíamos tener GraphQL Playground en funcionamiento con una consulta para devolver user { name }
nuestra matriz vacía.
Crear mutación
La sintaxis de nuestras mutaciones es casi la misma que la de nuestra consulta. Solo tenemos que declarar las opciones que queremos, agregar los argumentos (si los hay) y declarar el tipo que se debe devolver cuando se complete la operación.
En lugar de agregar todos nuestros argumentos en línea, es bastante común dividir los datos en su propio tipo especial llamado input
tipo para fines de organización. Es una convención de nomenclatura general que verá en herramientas como Prisma para nombrar la entrada como sea que el solucionador termine con la palabra entrada, por lo que addUser
obtiene una AddUserInput
entrada.
servidor.js
const typeDefs = ` type Mutation { addUser(data: AddUserInput): User! } input AddUserInput { name: String!, age: Int! }`;
Al igual que con las consultas, podemos acceder a los argumentos args
y agregar nuestro nuevo usuario a nuestra matriz y devolverlos.
const resolvers = { Query: {...}, Mutation: { addUser(parent, args, ctx, info) { const user = { ...args.data }; users.push(user); return user; } }}
Eliminar y actualizar mutaciones
Como la sintaxis es tan simple, resulta casi sin esfuerzo desarrollar las demás operaciones CRUD.
Sabremos qué elemento estamos eliminando o actualizando simplemente buscando al usuario por nombre.
servidor.js
const typeDefs = ` type Mutation { deleteUser(name: String!): User! updateUser(name: String!, data: UpdateUserInput): User! } input UpdateUserInput { name: String age: Int }`const resolvers = { Query: { ... }, Mutation: { deleteUser(parent, args, ctx, info) { // We're just finding the index of the user with a matching name, // checking if it exists, and removing that section of the array. const userIndex = users.findIndex(user = user.name.toLowerCase() === args.name.toLowerCase()); if (userIndex === -1) throw new Error('User not found'); const user = users.splice(userIndex, 1); return user[0]; }, updateUser(parent, args, ctx, info) { const user = users.find(user = user.name.toLowerCase() === args.who.toLowerCase()); if (!user) throw new Error('User not found'); // This way, only the fields that are passed-in will be changed. if (typeof args.data.name === "string") user.name = args.data.name; if (typeof args.data.age !== "undefined") user.age = args.data.age; return user; } }}
Ahora localhost:4000
puedes probar esta mutación y consultar nuestra matriz nuevamente.
mutation { addUser(data: { name: "Alli", age: 48 }) { name age }}
O en otra pestaña:
mutation { updateUser(name: "Alli", data: { name: "Crusher", age: 27 }) { name age }}
Suscripciones
Podemos usar el Subscription
tipo especial para poder observar cualquier cambio en nuestros datos. La sintaxis es muy similar a la de las consultas y mutaciones, solo agrega el tipo Subscription
, agrega lo que quieras que observe y lo que quieras que se devuelva. Vamos a devolver un tipo personalizado que nos enviará de vuelta nuestros datos modificados y nos dirá si fue una operación de creación, eliminación o actualización.
Para usar suscripciones, vamos a tener que usar PubSub
graphql-yoga e inicializarlo antes que todo lo demás. En nuestro solucionador de suscripciones, usaremos una función llamada subscribe
que deberá devolver un evento asincrónico, al que llamaremos user
. Siempre que queramos conectar algo a esta suscripción, usaremos este nombre de evento.
servidor.js
import { GraphQLServer, PubSub } from 'graphql-yoga';const pubsub = new PubSub();const typeDefs = `type Subscription { user: UserSubscription!}type UserSubscription { mutation: String! data: User!}`const resolvers = { Query: { ... }, Mutation: { ... }, Subscription: { user: { subscribe() { return pubsub.asyncIterator('user'); } }}}
Nuestra suscripción está configurada y disponible en los documentos GraphQL generados, pero no sabe cuándo activarse ni qué devolver. En nuestras mutaciones, agregaremos pubsub.publish
un enlace a nuestro user
evento y pasaremos nuestros datos.
const resolvers = { Query: { ... }, Mutation: { addUser(parent, args, ctx, info) { const user = { ...args.data }; users.push(user); // We'll just link it to our user event, // and return what type of mutation this is and our new user. pubsub.publish("user", { user: { mutation: "Added", data: user } }); return user; }, deleteUser(parent, args, ctx, info) { const userIndex = users.findIndex( user = user.name.toLowerCase() === args.who.toLowerCase() ); if (userIndex === -1) throw new Error("User not found"); const user = users.splice(userIndex, 1); pubsub.publish("user", { user: { mutation: "Deleted", data: user[0] } }); return user[0]; }, updateUser(parent, args, ctx, info) { const user = users.find( user = user.name.toLowerCase() === args.who.toLowerCase() ); if (!user) throw new Error("User not found"); if (typeof args.data.name === "string") user.name = args.data.name; if (typeof args.data.age !== "undefined") user.age = args.data.age; pubsub.publish("user", { user: { mutation: "Updated", data: user } }); return user; } }, Subscription: { ... }};
En la localhost:4000
pestaña podemos abrir una nueva y ejecutar la siguiente suscripción. Deberías ver un mensaje que diga "escuchando..." con una pequeña rueda giratoria. En otra pestaña, podemos ejecutar cualquiera de nuestras otras mutaciones anteriores y nuestra suscripción devolverá automáticamente lo que se hizo y lo que se modificó.
subscription { user { mutation data { name age } }}
Conclusión
Espero que esto haya sido útil para comprender cómo configurar las API de GraphQL con mutaciones y suscripciones. Si tuvo algún problema al configurar esto, siempre puede consultar este repositorio.
Deja una respuesta