#はじめに
GraphQLの概要の紹介、QueryとMutationの動きを確認した前回の続きです。
本記事はGraphQLのSubscriptionについての投稿となります。
Subscription(サブスクリプション)とは直訳で「購読」という意味で、月額課金サービスなどの総称で耳馴染みのある言葉ですが、
GraphQLにおいては、Mutationにおいてデータ操作をした際に、クライアント側でサーバーから発行された変更情報を受け取ることを指し、チャットアプリや通知機能、ログイン認証などでの利用が考えられます。
こちらの解説が大変分かりやすいので、オススメです。
今回は前回サーバーを立てたExpress-GraphQLに代わりApolloを利用して、GraphQLサーバーを立てた上でSubscriptionの動きを確認していきたいと思います。
##Apolloとは
Apolloは、データグラフを構築するためのプラットフォームであり、アプリケーションクライアント(ReactやiOSアプリなど)をバックエンドサービスにシームレスに接続する通信レイヤーです。
クライアントとサーバーの中間にあるApolloサーバーによって、GraphQLによるデータのやり取りがスムーズになります。
##下準備
プロジェクトを作った後に、npm経由でapollo-serverをインストールします。
mkdir graphql-server
cd graphql-server
mkdir resolver
touch index.js db.js resolver/{Query.js,Mutation.js,Subscription.js}
npm init
npm install apollo-server apollo-server-express --save
##サンプルコードを読みやすくするために
###PubSub
PubSubはSubscriptionによる通知イベントを作成する工場のような存在。
const { PubSub } = require('apollo-server');
const pubsub = new PubSub();
###gql
gqlというタグ付きテンプレートリテラルと呼ばれる特別な関数を使用して、GraphQLに関連する定義をJavaScripの文字列として表現できるようにします。
const typeDefs = gql`
type Subscription {
postAdded: Post
}
type Query {
posts: [Post]
}
type Mutation {
addPost(author: String, comment: String): Post
}
type Post {
author: String
comment: String
}
`
###Resolver引数
リゾルバ関数には固定の役割を持つ4つの引数があります。
引数(昇順) | 説明 |
---|---|
parent | 親リゾルバからの戻り値。 |
args | フィールドに提供されたすべてのGraphQL引数を含むオブジェクト |
context | 特定の操作に対して実行されるすべてのリゾルバ間で共有されます。これを使用して、認証情報やデータソースへのアクセスなどの操作ごとの状態を共有します。 |
info | フィールド名、ルートからフィールドへのパスなど、操作の実行状態に関する情報 |
##コードの紹介と解説
作成したファイルにコードを書いていきます。
QueryとMutationについての概要は省略致しますので、はじめてGraphQLに触られる方はこちらの記事からどうぞ!😊
先ず、データベース代わりのJavaScriptファイルを用意します。
const posts = [{
id: '1',
title: 'こころ',
author: '夏目漱石'
}, {
id: '2',
title: '舞姫',
author: '森鴎外'
}, {
id: '3',
title: '羅生門',
author: '芥川龍之介'
}]
const db = {
posts,
}
module.exports = db;
続いてQuery,Mutation,そして今回のテーマであるSubscriptionのリゾルバ関数を書いたファイルを順番に用意していきます。
const Query = {
posts(parent, args, { db }, info) {
//クエリを書いた時に引数が「ない」時
//模擬データベースの内容を全て表示
if (!args.query) {
return db.posts
//クエリを書いた時に引数が「ある」時
//引数とtitle or authorが一致したものだけを表示
}else{
return db.posts.filter((post) => {
const isTitleMatch = post.title.toLowerCase().includes(args.query.toLowerCase())
const isAuthorMatch = post.author.toLowerCase().includes(args.query.toLowerCase())
return isTitleMatch || isAuthorMatch
})
}
}
}
module.exports = Query
クエリのリゾルバ関数です。引数の有無で条件分岐をしています。
JavaScriptで条件を細かく書くことで、「本当に必要な情報だけを取得できること」がGraphQLの強みです。
const Mutation = {
createPost(parent, args, { db, pubsub }, info) {
const postNumTotal = String(db.posts.length + 1)
const post = {
id: postNumTotal,
...args.data
}
//データベース更新
db.posts.push(post)
//Subscription着火
pubsub.publish('post', {
post: {
mutation: 'CREATED',
data: post
}
})
return post
},
updatePost(parent, args, { db, pubsub }, info) {
const { id, data } = args
const post = db.posts.find((post) => post.id === id)
if (!post) {
throw new Error('Post not found')
}
if (typeof data.title === 'string' && typeof data.author === 'string') {
//データベース更新
post.title = data.title
post.author = data.author
console.log(post)
//Subscription着火
pubsub.publish('post', {
post: {
mutation: 'UPDATED',
data: post
}
})
}
return post
},
deletePost(parent, args, { db, pubsub }, info) {
const post = db.posts.find((post) => post.id === args.id)
const postIndex = db.posts.findIndex((post) => post.id === args.id)
if (postIndex === -1) {
throw new Error('Post not found')
}
//データベース更新
db.posts.splice(postIndex, 1)
//Subscription着火
pubsub.publish('post', {
post: {
mutation: 'DELETED',
data: post
}
})
return post
},
}
module.exports = Mutation
Mutationのリゾルバ関数では、データベースの更新とSubscriptionの着火をしています。
const Subscription = {
post: {
subscribe(parent, args, { pubsub }, info) {
return pubsub.asyncIterator('post')
}
}
}
module.exports = Subscription
Subscriptionのリゾルバ関数です。
pubsub.asyncIteratorでサブスクリプションのイベントを非同期でリッスンします。
説明の関係で最後になりましたが、
スキーマの定義とサーバー起動のファイルになります。
const {ApolloServer,PubSub,gql} = require('apollo-server');
const db = require('./db')
const Query = require('./resolver/Query')
const Mutation = require('./resolver/Mutation')
const Subscription = require('./resolver/Subscription')
//スキーマ定義
const typeDefs = gql`
type Query {
posts(query: String): [Post!]!
}
type Mutation {
createPost(data: CreatePostInput!): Post!
deletePost(id: ID!): Post!
updatePost(id: ID!, data: UpdatePostInput!): Post!
}
# Subscription
type Subscription {
post: PostSubscriptionPayload!
}
input CreatePostInput {
title: String!
author: String!
}
input UpdatePostInput {
title: String
author: String!
}
type Post {
id: ID!
title: String!
author: String!
}
######################
# Subscriptionで利用
######################
enum MutationType {
CREATED
UPDATED
DELETED
}
# Subscriptionのフィールド
type PostSubscriptionPayload {
mutation: MutationType!
data: Post!
}
`
//PubSubのインスタンスを作成,Subscriptionが利用可能に!
const pubsub = new PubSub()
const server = new ApolloServer({
typeDefs: typeDefs,
resolvers: {
Query,
Mutation,
Subscription,
},
context: {
db,
pubsub
}
})
server.listen().then(({ url, subscriptionsUrl }) => {
console.log(`🚀 Server ready at ${url}`);
console.log(`🚀 Subscriptions ready at ${subscriptionsUrl}`);
});
サーバーを立てる際にスキーマやリゾルバ、PubSubなどを引数に指定しています。
指定した引数はQueryやMutationそしてSubscription、それぞれの処理で利用をしています。
準備が出来たらターミナルから起動させます。
node index.js
🚀 Server ready at http://localhost:4000/
🚀 Subscriptions ready at ws://localhost:4000/graphql
前回同様にGraphQL IDEにてテストをしてみます。
Subscriptionを利用するには、クエリを書いた後にYouTubeの再生ボタンのような矢印をクリックします。
写真のように赤いポーズボタンに変化したら、Subscriptionの起動成功です。
そのままSubscriptionのタブに移動して、結果を確認します。
Apolloサーバーからデータが新しく作成されたことに対する変更情報を受け取ることが出来ました。
データの更新、そして削除についても確認します。
Subscriptionにてデータの削除の確認(更新と削除を連続して行っています)
以上、Mutationによるデータの更新をSubscriptionを通して確認することが出来ました!!🚀🚀
###おわりに
QueryやMutationと比較してSubscriptionは理解に時間がかかったものの、
実際にコードを書き、GraphQL IDEでテストをすることによって自分の中で腹落ちさせることが出来ました。
次回の記事ではフロントにVueを使った上で、実際のアプリケーションの中でGraphQLを使っていきます!
それでは、また!😊