😎

ApolloでgraphQLのsubscriptionを実装したはなし

2023/08/09に公開

subscriptionってなんぞや

graphQLのサブスクリプションは、クライアントがリアルタイムのデータ更新を受け取ることができるようにする機能です。
これは、サーバーがクライアントに対してプッシュ通知を送ることが可能になっています。

サブスクリプションは、クエリやミューテーションと同じように、GraphQLのスキーマの一部として定義されます。しかし、クエリやミューテーションが一回のリクエスト-レスポンスサイクルで完結するのに対して、サブスクリプションは長期間にわたって維持される持続的な接続を必要とします。

この持続的な接続は、通常WebSocketを使用して確立されます。クライアントはサブスクリプションを開始するリクエストを送信し、その後サーバーは新しいデータが利用可能になったときにそれをクライアントにプッシュします。

サブスクリプションのリゾルバは、通常のリゾルバと異なり、非同期イテラブルまたは非同期イテレータを返します。これは、新しいデータが利用可能になるたびに新しい値を生成しています。

例えば、あるユーザーが新しいメッセージを投稿すると、そのメッセージはサーバーによって特定のサブスクリプションイベント(例えばMESSAGE_ADDED)をトリガーします。
このイベントは、そのサブスクリプションに対してリスニングしている全てのクライアントにブロードキャストされ、それぞれのクライアントは新しいメッセージを受け取って更新を行います。

具体的な実装


import { ApolloServer, gql } from 'apollo-server-express';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { createServer } from 'http';
import express from 'express';
import { execute, subscribe } from 'graphql';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { PubSub } from 'graphql-subscriptions';

const pubsub = new PubSub();
const POST_ADDED = 'POST_ADDED';

const typeDefs = gql`
  type Post {
    author: String
    comment: String
  }

  type Query {
    posts: [Post]
  }

  type Mutation {
    addPost(author: String, comment: String): Post
  }

  type Subscription {
    postAdded: Post
  }
`;

interface Post {
  author: string;
  comment: string;
}

let posts: Post[] = [];

const resolvers = {
  Query: {
    posts: () => posts,
  },
  Mutation: {
    addPost: (_: any, { author, comment }: { author: string; comment: string }): Post => {
      const post = { author, comment };
      posts.push(post);
      pubsub.publish(POST_ADDED, { postAdded: post });
      return post;
    },
  },
  Subscription: {
    postAdded: {
      subscribe: () => pubsub.asyncIterator([POST_ADDED]),
    },
  },
};

const schema = makeExecutableSchema({ typeDefs, resolvers });

const app = express();
const httpServer = createServer(app);

const server = new ApolloServer({
  schema,
});

server.start().then(() => {
  server.applyMiddleware({ app });

  SubscriptionServer.create(
    {
      schema,
      execute,
      subscribe,
    },
    {
      server: httpServer,
      path: server.graphqlPath,
    }
  );

  httpServer.listen({ port: 4000 }, (): void => {
    console.log(`サーバーの開始 http://localhost:4000${server.graphqlPath}`);
  });
});

PubSubインスタンスとイベント名の定義

PubSubインスタンスを作成し、サブスクリプションイベントの名前を定義しています。

const pubsub = new PubSub();
const POST_ADDED = 'POST_ADDED';

スキーマの定義

GraphQLのスキーマを定義しています。これには、Postタイプ、クエリ、ミューテーション、およびサブスクリプションが含まれます。

const typeDefs = gql`
	type Post {
		author: String
		comment: String
	}

	type Query {
		posts: [Post]
	}

	type Mutation {
		addPost(author: String, comment: String): Post
	}

	type Subscription {
		postAdded: Post
	}
`;

リゾルバの定義

クエリ、ミューテーション、およびサブスクリプションのリゾルバを定義しています。

const resolvers = {
	Query: {
		posts: () => posts,
	},
	Mutation: {
		addPost: (_: any, { author, comment }: { author: string; comment: string }): Post => {
		  const post = { author, comment };
		  posts.push(post);
		  pubsub.publish(POST_ADDED, { postAdded: post });
		  return post;
		},
	},
	Subscription: {
		postAdded: {
		  subscribe: () => pubsub.asyncIterator([POST_ADDED]),
		},
	},
};

サーバーのセットアップ

Expressアプリケーション、HTTPサーバー、およびApollo Serverをセットアップし、サーバーを起動しています。

const app = express();
const httpServer = createServer(app);

const server = new ApolloServer({
    schema,
});

server.start().then(() => {
server.applyMiddleware({ app });

SubscriptionServer.create(
{
  schema,
  execute,
  subscribe,
},
{
  server: httpServer,
  path: server.graphqlPath,
}
);

httpServer.listen({ port: 4000 }, (): void => {
	console.log(`サーバーの開始 http://localhost:4000${server.graphqlPath}`);
});

このコード全体は、Apollo Serverを使用してGraphQL APIを作成し、リアルタイムの更新をサポートするサブスクリプションを設定する方法を示しています。新しいポストが追加されると、それはPOST_ADDEDイベントとして発行され、postAddedサブスクリプションがそのイベントを購読しています。

Discussion