GraphQL と Nuxt.js でチャットを作る

@asya_aoi1049 on Sat Feb 02 2019
12.2 min

目次

この記事はNuxt.js #2 Advent Calendar 2018の18日の記事です。
前の記事はNuxtでApolloを使わずにGraphQLを扱ってみる です。GraphQLの記事が続きますね。

はじめに

近年、GraphQLの知名度も上がり、RESTfulAPI に疲弊したプロジェクトがGraphQLを採用することが多くなっていると感じます。
GraphQLの詳しい説明は省略しますが、データ読み込みのquery, データ書き込みのmutation、リアルタイム接続のsubscriptionがあります。

query, mutationに比べてsubscriptionについての記事が少ないと感じたため、この記事ではSubscriptionを使用した簡単なチャットをNuxt.jsで作成します。

Demo

画面上は一般的なチャットですが、GraphQLのSubscriptionを使用しています。
demo

githubにソースをあげていますので、そちらも参考にしてください。

GraphQLについて

バックエンドはGolangのgo-gqlgenを使用してGraphQLを実装しています。
GolangのAdventCalendarではないので省略しますが、主要なスキーマは以下のようになっています。

type

type Message {
  id: String!
  user: String!
  createdAt: Time!
  text: String!
}

query

users [String!]!
messages: [Message!]!

subscription

messagePosted(
    user: String!
  ): Message!
userJoined(
    user: String!
  ): String!

Nuxt.jsの話

今回は、Nuxt.jsとapolloを使用します。デザインは最近イチオシと聞くBuefyです。

準備

Nuxtのプロジェクトを作成後を前提として、apolloをインストールします。githubを参考にインストールします。

npm install --save @nuxtjs/apollo
npm install --save graphql-tag

nuxt.config.js に以下を追加します。

module.exports = {
  modules: [,
    'nuxt-buefy',
    '@nuxtjs/apollo', // added
  ],
  // added 
  apollo: {
    clientConfigs: {
      default: {
        httpEndpoint: 'http://GraphQLのサーバ/query',
        wsEndpoint: 'ws://GraphQLのサーバ/query',
        websocketsOnly: true, 
      },
    }
  }

チャットに参加

本来は認証をちゃんとしなければならないのですが、簡単にするためStoreに保存しています。

<template>
  <div class="modal-card">
    <section class="modal-card-body">
      <h3 class="title has-text-centered has-text-dark">Create User</h3>
      <div class="box">
        <b-field label="UserName">
          <b-input v-model="user" type="user" placeholder="user name">
          </b-input>
        </b-field>

        <button class="button is-dark is-large is-fullwidth" @click="join()">
          Join
        </button>
      </div>
    </section>
  </div>
</template>
<script>
export default {
  name: 'LoginModal',
  data () {
    return {
      user: '',
    }
  },
  methods: {
    join() {
      this.$store.commit('users/join', this.user)
      this.$router.push('/chat')
    }
  }
}
</script>

チャットの画面

主要なところを抜粋します。

ユーザ一覧

<template>
    <div>
      ユーザ一覧
      <b-taglist>
        <b-tag type="is-light" size="is-large" v-for="user in users" :key="user" :style="isSelf(user)">{{user}}</b-tag>
      </b-taglist>
    </div>
</template>
<script>
import QUsers from '@/apollo/queries/users.gql'
import SUserjoined from '@/apollo/subscriptions/userJoined.gql'
export default {
  apollo: {
    users: { // ★1
      query: QUsers, // ★2
      subscribeToMore: { // ★3
        document: SUserjoined, // ★4
        variables () {
          return {
            user: this.$store.state.users.user
          }
        },
        updateQuery: (prev, { subscriptionData }) => { // ★5
          if (!subscriptionData.data) {
            return prev
          }
          const user = subscriptionData.data.userJoined
          if (prev.users.find(u => u === user)) {
            return prev
          }
          return Object.assign({}, prev, {
            users: [user, ...prev.users],
          })
        }
      },
   }
}
</script>

★1で★2のQueryを呼びます。★2の結果がHTML側で★1のプロパティ(ここではusers)を使用することができます。
★3からsubscriptionについての処理になります。
★4は下記のsubscriptionsを指定しており、データが更新されたら★5の処理を行います。

# users.gql
query {
  users
}
# userJoined.gql
subscription($user: String!) {
  userJoined(user: $user)
}

メッセージ一覧

メッセージ一覧もユーザ一覧と同様に、クエリにsubscribeToMoreプロパティを追加するようにします。

<script>
export default {
  apollo: {
      query: QMessages,
      subscribeToMore: {
        document: SMessagePosted,
        variables () {
          return {
            user: this.$store.state.users.user
          }
        },
        updateQuery: (prev, { subscriptionData }) => {
          if (!subscriptionData.data) {
            return prev
          }
          const message = subscriptionData.data.messagePosted
          if (prev.messages.find(m => m.id === message.id)) {
            return prev
          }
          return Object.assign({}, prev, {
            messages: [message, ...prev.messages],
          })
        }
      },
   }
}

まとめ

数行nuxt.config.jsに追加し、subscribeToMoreを設定するだけで簡単にGraphQLのsubscriptionが使用できます。
websocketでAPIを設計するときはGraphQLも視野に入れてもいいと思います。

余談ですが、簡単なAPIを作成するのにGolangは向いていると思います。

参考

Real-time Chat with GraphQL Subscriptions in Go

日別に記事を見る