この記事では、MutationおよびSubscriptionタイプを使用して、GraphQLでクエリを実行するだけでなく、データを操作して変更を監視する方法について説明します。 公式ドキュメントで詳細をお気軽に見つけてください。

簡単にするために、データベースやHTTPリクエストは使用しませんが、スキーマとリゾルバーを使用して基本的なAPIを設定する方法を知っている必要があります。

インストール

サーバーをセットアップするためにgraphql-yogaライブラリを使用し、サーバーを自動的にリロードするためにnodemonを使用します。 また、JavaScriptの最新機能を使用できるように、Preprosbabelなどのプリプロセッサーも必要です。

$ npm i graphql-yoga nodemon

ボイラープレートのセットアップ

サーバーのセットアップに加えて、空のusers配列と、すべてのユーザーを返すための単純なスキーマとリゾルバーがあります。

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

出力ファイルでnodemonを実行するstartスクリプトが必要です。

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

これで、ターミナルでnpm run startを実行できます。

localhost:4000で、 GraphQL Playground を起動して実行し、user { name }のクエリで空の配列を返す必要があります。

ミューテーションを作成する

ミューテーションの構文は、クエリの構文とほぼ同じです。 必要なオプションを宣言し、引数(存在する場合)を追加し、完了時に返されるタイプを宣言するだけです。

すべての引数をインラインで追加する代わりに、整理のためにデータをinputタイプと呼ばれる独自の特殊なタイプに分割するのが一般的です。 これは、 Prisma などのツールで見られる一般的な命名規則であり、リゾルバーがinputという単語で終わるものに関係なく入力に名前を付けるため、addUserAddUserInput入力を取得します。

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

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

クエリの場合と同様に、argsの引数にアクセスし、新しいユーザーを配列に追加して、それらを返すことができます。

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

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

ミューテーションの削除と更新

構文が非常に単純なため、他のCRUD操作を具体化するのはほとんど簡単です。

名前でユーザーを検索するだけで、削除または更新するアイテムがわかります。

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

localhost:4000で、このミューテーションを試して、配列を再度クエリできます。

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

または別のタブで:

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

サブスクリプション

特別なSubscriptionタイプを使用して、データの変更を監視できます。 構文はクエリやミューテーションの構文と非常によく似ています。タイプSubscriptionを追加し、監視したいものを追加し、返されるものを追加するだけです。 変更されたデータと、それが作成、削除、または更新のいずれの操作であったかを通知するカスタムタイプを返します。

サブスクリプションを使用するには、graphql-yogaのPubSubを使用し、何よりも先に初期化する必要があります。 サブスクリプションリゾルバーでは、subscribeという関数を使用します。この関数は、userという名前の非同期イベントを返す必要があります。 このサブスクリプションに何かを接続する場合は常に、このイベント名を使用します。

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

サブスクリプション自体はセットアップされ、生成されたGraphQLドキュメントで利用できますが、いつ起動するか、何を返すかはわかりません。 ミューテーションに戻って、pubsub.publishを追加して、userイベントにリンクし、データを返します。

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

localhost:4000で、新しいタブを開いて次のサブスクリプションを実行できます。 小さな糸車で「聞いている…」というメッセージが表示されるはずです。 別のタブで、他の過去のミューテーションを実行できるようになりました。サブスクリプションは、実行された内容と変更された内容を自動的に返します。

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

結論

これが、ミューテーションとサブスクリプションを使用してGraphQLAPIをセットアップする方法を理解するのに役立つことを願っています。 これを設定する際に問題が発生した場合は、いつでもこのリポジトリを確認できます。