vue-apolloでのsubscriptionの設定方法（リアルタイム同期のChatアプリのサンプル付き）

この記事はVue.js Advent Calendar 23日目の記事です。

リアルタイム更新というとFirebase Firestoreが思い浮かびますが、
実はGraphQLのsubscriptionを使っても手軽に出来ます!
vue-apolloでのsubscriptionの設定についての日本語記事があまりなかったので今回紹介します。


# サンプルアプリ
「説明いらんから、はよコード」という方は、こちらから。
https://github.com/kawamataryo/hasura-vue-chat-app

subscriptionsの練習用に作ったリアルタイム同期のチャットアプリです。
HasuraでGraphQLサーバーを立てて、vue-apolloでsubscriptionを行っています。
READMEの通り起動すれば数分でhasura, vue-apolloのsubscriptionを体験できます。
（練習用に作ったので優しい目でお願いします。。）

![Dec-21-2019 16-44-3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/96985810-8595-1a10-4114-0935fa7ead6c.gif)

# Subscriptionとは？
GraphQLでのWebSocketを使ったリアルタイムデータ通信の手段です。
subscriptionsを使うと、更新された内容をページリロードなしにリアルタイムでサーバーから、Webページに直接通知することが出来ます。


# vue-apolloの設定
では本題のvue-apolloのsubscriptionの設定です。
ポイントは、クエリの種別でhttp通信にするか、websocket通信にするか設定しているところです。
`new HttpLink`でhttp、`new WebSocketLink`でwebsocketのエンドポイントを設定し、
`split`関数で呼ばれるクエリによって向き先を変更しています。

```js
import Vue from "vue";
import { ApolloClient } from "apollo-client";
import { HttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
import { split } from "apollo-link";
import { WebSocketLink } from "apollo-link-ws";
import { getMainDefinition } from "apollo-utilities";
import VueApollo from "vue-apollo";

// 通常のquery, mutationsで使われるhttp通信のエンドポイントを指定
const httpLink = new HttpLink({
  uri: "http://localhost:8000/v1/graphql"
  // トークンによる認証を追加する場合は以下のように設定
  // headers: {
  //   authorization: `Bearer xxxxx`
  // }
});

// subscriptionで使われるwebsocket通信のエンドポイントを指定
const wsLink = new WebSocketLink({
  uri: "ws://localhost:8000/v1/graphql",
  options: {
    reconnect: true
 // トークンによる認証を追加する場合は以下のように設定
 // headers: {
 //   authorization: `Bearer xxxxx`
 // }
  }
});

// GraphQLのエンドポイントの向き先をクエリの種別で分離するための設定
// 参考 https://www.apollographql.com/docs/link/composition/#directional-composition
const link = split(
  ({ query }) => {
    // クエリから種別を取得してsubscriptionの場合は、wsLink（websocket）を利用する。
    const definition = getMainDefinition(query);
    return (
      definition.kind === "OperationDefinition" &&
      definition.operation === "subscription"
    );
  },
  wsLink,
  httpLink
);

// Apollo Clientの作成
const apolloClient = new ApolloClient({
  link,
  cache: new InMemoryCache(),
  connectToDevTools: true
});

Vue.use(VueApollo);

// vue-apolloへの接続
export const apolloProvider = new VueApollo({
  defaultClient: apolloClient
});

```


# subscribeの実行
[サンプルアプリのコード](https://github.com/kawamataryo/hasura-vue-chat-app/blob/7a04ff435b01930125e60a0cf1f8559494fb275b/src/components/Board.vue#L91)をもとに説明します。
通常のvue-apolloのquery内に`subscriptionsToMore`を追加することでそのqueryに紐づくデータをsubscriptionsの結果でリアルタイム更新することが出来ます。
`document`にsubscriptionのクエリを設定し、対象のデータに変更がある場合、`updateQuery`で設定した関数が呼ばれ、queryに紐づくデータ（この場合は`messages`）が更新されます。




```js
export default {
  data() {
    return {
      messages: []
    }
  },
  apollo: {
    messages: {
      query: gql`
        query {
          messages {
            content
            userId: user_id
            iconColor: icon_color
            id
          }
        }
      `,
      subscribeToMore: {
        document: gql`
          subscription {
            messages {
              id
              userId: user_id
              iconColor: icon_color
              content
            }
          }
        `,
        updateQuery: (previousResult, { subscriptionData }) => {
          return subscriptionData.data;
        }
      }
    }
  .
  .
  .
}
```

# 終わりに
以上、vue-apolloでのsubscriptionsの設定方法でした。
リアルタイム更新をしようと思うとぱっと思いつくのはFirestoreですが、GraphQLのSubscriptionも手軽でおすすめです。（Hasuraを使えばなおさら）

vue-apolloは、上手くApolloをVue.js仕様にラップしていて僅かなコードでGraphQL触れるのでとても良いですね。

# 参考
https://vue-apollo.netlify.com/guide/apollo/subscriptions.html#setup
https://www.howtographql.com/vue-apollo/8-subscriptions/