Mutations et abonnements dans GraphQL

De Get Docs
Aller à :navigation, rechercher

Dans cet article, nous examinerons l'utilisation des types Mutation et Subscription pour manipuler et surveiller nos données pour les modifications, au lieu de simplement interroger, dans GraphQL. N'hésitez pas à en découvrir plus dans les docs officiels.

Pour simplifier les choses, nous n'utiliserons aucune base de données ou requête HTTP, mais il est nécessaire de savoir comment configurer une API de base avec schémas et résolveurs.

Installation

Nous utiliserons la bibliothèque graphql-yoga pour configurer notre serveur et nodemon pour qu'il se recharge automatiquement. Nous aurons également besoin d'un pré-processeur comme Prepros ou babel afin de pouvoir utiliser les dernières fonctionnalités de JavaScript.

$ npm i graphql-yoga nodemon

Configuration standard

Outre la configuration de notre serveur, nous avons juste un tableau users vide et un schéma et un résolveur simples pour renvoyer tous nos utilisateurs.

serveur.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'));

Nous aurons besoin d'un script start qui exécutera nodemon sur notre fichier de sortie :

package.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"
}

Maintenant, dans le terminal, vous pouvez simplement exécuter npm run start.

À localhost:4000, nous devrions avoir GraphQL Playground opérationnel avec une requête pour user { name } renvoyant notre tableau vide.

Créer une mutation

La syntaxe de nos mutations est presque la même que celle de notre requête. Nous avons juste besoin de déclarer les options que nous voulons, d'ajouter des arguments (le cas échéant) et de déclarer quel type doit être renvoyé une fois terminé.

Au lieu d'ajouter tous les arguments en ligne, il est assez courant de diviser les données en son propre type spécial appelé type input pour des raisons d'organisation. C'est une convention de dénomination générale que vous verrez dans des outils comme Prisma pour nommer l'entrée quelle que soit la fin du résolveur par le mot input, donc addUser obtient une entrée AddUserInput.

serveur.js

const typeDefs = `
  type Mutation {
    addUser(data: AddUserInput): User!
  }

  input AddUserInput {
    name: String!, 
    age: Int!
  }
`;

Tout comme avec les requêtes, nous pouvons accéder aux arguments sur args et ajouter notre nouvel utilisateur à notre tableau, et les renvoyer.

const resolvers = {
  Query: {...},
  Mutation: {
    addUser(parent, args, ctx, info) {
      const user = { ...args.data };

      users.push(user);
      return user;
    }
  }
}

Supprimer et mettre à jour les mutations

La syntaxe étant si simple, il est presque facile d'étoffer les autres opérations CRUD.

Nous saurons quel élément nous supprimons ou mettons à jour simplement en recherchant l'utilisateur par son nom.

serveur.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;
    }
  }
}

Maintenant, à localhost:4000, vous pouvez essayer cette mutation et interroger à nouveau notre tableau.

mutation {
  addUser(data: {
    name: "Alli",
    age: 48
  }) {
    name
    age
  }
}

Ou dans un autre onglet :

mutation {
  updateUser(name: "Alli", data: {
    name: "Crusher",
    age: 27
  }) {
    name
    age
  }
}

Abonnements

Nous pouvons utiliser le type spécial Subscription pour nous permettre de surveiller toute modification de nos données. La syntaxe est très similaire à celle des requêtes et des mutations, ajoutez simplement le type Subscription, ajoutez ce que vous voulez qu'il regarde et ce que vous voulez renvoyer. Nous allons renvoyer un type personnalisé qui nous renverra à la fois nos données modifiées et nous dira s'il s'agissait d'une opération de création, de suppression ou de mise à jour.

Pour utiliser les abonnements, nous devrons utiliser PubSub de graphql-yoga et l'initialiser avant tout le reste. Dans notre résolveur d'abonnement, nous utiliserons une fonction appelée subscribe qui devra renvoyer un événement asynchrone, que nous nommerons user. Chaque fois que nous voulons connecter quelque chose à cet abonnement, nous utiliserons ce nom d'événement.

serveur.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');
      }
    }
}
}

Notre abonnement lui-même est configuré et disponible dans les documents GraphQL générés, mais il ne sait pas quand se déclencher ni quoi renvoyer. De retour dans nos mutations, nous ajouterons pubsub.publish pour établir un lien avec notre événement user et retransmettre nos données.

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: { ... }
};

Sur localhost:4000, nous pouvons ouvrir un nouvel onglet et exécuter l'abonnement suivant. Vous devriez voir un message « écoute… » avec une petite roue qui tourne. Dans un autre onglet, nous pouvons maintenant exécuter n'importe laquelle de nos autres mutations passées et notre abonnement renverra automatiquement ce qui a été fait et ce qui a été modifié.

subscription {
  user {
    mutation
    data {
      name
      age
    }
  }
}

Conclusion

J'espère que cela a été utile pour comprendre comment configurer vos API GraphQL avec des mutations et des abonnements. S'il y a eu des problèmes lors de la configuration, vous pouvez toujours consulter ce référentiel.