はじめに
Ateam Lifestyle x cyma Advent Calendar 2018の25日目は、 株式会社エイチームライフスタイル のエンジニア @sakupa80 が担当します。
最近よくHeadlessCMSなどで使われているGraphQLの話をよく聞くので、チュートリアルがてらに触ってみました。
バックエンドは普段は開発に使っているRailsを使用し、フロントエンド側はとっかかりやすいVue.jsを選びました。
この記事の順序どおりに進めればGraphQLの基本は学べるようにしておりますので、これからGraphQLを触ってみたいという方の参考になれば幸いです。
開発環境
- macOS High Sierra
- Ruby 2.5.1
- Rails 5.2.2
- graphql-ruby 1.8.11
- Vue 2.5.2
- MySQL 5.7.21
graphql-rubyは1.8なのでclassベースでの記述で進めます。
環境構築
まずはRailsから作っていきます。
ディレクトリgraphql_railsを作り、
$ mkdir graphql_rails ; cd $_
$ bundle init
を叩いてGemfileを生成されました。
少し編集します。
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.5.1'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.2.2'
次にbundle install
を叩きます。
$ bundle install --path vendor/bundler
インストールが終わりましたら、rails new
を実行します。
フロントにはVue.jsを使うので、optionでVueを指定しておきましょう。
$ bundle exec rails new . --webpack=vue
出来たら次にGraphQL用のgemなどを追記しておきます。
gem 'mysql2'
gem 'graphql'
gem 'graphiql-rails'
追記したらもう一度bundle install
。
$ bundle install
graphql-rubyが入り、Railsのgenerateでgraphql環境を簡単に作れるようになっているので、下記コマンドを実行。
$ bundle exec rails g graphql:install
すると、/app配下に新たなディレクトリが生成されました。
.
├── graphql_rails_schema.rb
├── mutations
└── types
├── base_enum.rb
├── base_input_object.rb
├── base_interface.rb
├── base_object.rb
├── base_scalar.rb
├── base_union.rb
├── mutation_type.rb
└── query_type.rb
データベースはmysqlを使うので、
立ち上げ&database.ymlも編集しておきましょう。
$ mysql.server start
default: &default
adapter: mysql2
encoding: utf8
database: rails_development
username: root
password:
今回はGraphQLを使って単純な記事データの受け渡しをする想定なので、
PostのModelを生成しておきます。
タイトルとコンテンツのカラムのみで大丈夫です。
フロントとのエンドポイントとなるHomeContollorを生成しておきます。
$ bundle exec rails g controller Home index
$ bundle exec rails g model Post title:string content:text
$ bundle exec rails db:create
$ bundle exec rails db:migrate
GraphQL用のobjectも生成しておきます。
$ bundle exec rails g graphql:object Post id:ID! title:String! content:String!
これで準備は出来ました!
実際にサーバーを立ち上げてブラウザで確認してみましょう。
$ bundle exec rails s -p 5000
graphql-rubyではgenarateするだけで実行できるテストクライアントを用意してくれています。
routes.rbを確認するとテストクライアントにアクセスできるrouteが追加されているのが確認できます。
Rails.application.routes.draw do
if Rails.env.development?
mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
end
post "/graphql", to: "graphql#execute"
...
end
http://localhost:5000/graphiql
にアクセスしてみましょう。
では実際にGraphQLを実装していきましょう。
queryとmutation
実際にテストクライアントでリクエストを送ってみましょう。
そのまえにGraphQLでは、
データの「読み」の場合は、query。
データの「作成」「更新」「削除」などの場合は、mutationを使います。
queryで記事全件取得
まずはGraphQLのPostオブジェクトにアクセスするためfieldを作成します。
module Types
class PostType < Types::BaseObject
field :id, ID, null: false
field :title, String, null: false
field :content, String, null: false
end
end
上記のようにPostTypeオブジェクトのアクセスしたいカラム名を書く必要があります。
次にquery_type.rbに記事データをリードできるfieldを追加しましょう。
module Types
class QueryType < Types::BaseObject
field :all_posts, [PostType], null: false
def all_posts()
Post.all
end
end
end
引数も何も渡さず、ただPost.allしているだけですね。
そして、このままだとCSRFトークンが無いと怒られちゃうので、今回は特別にトークン判定をSkipするようにしておきましょう。
class GraphqlController < ApplicationController
skip_before_action :verify_authenticity_token
...
次に確認のために事前にrailsコンソールなどからダミーデータを作っておきましょう。
ダミーデータを作ったら下記のqueryをテストクライアントで渡してみます。
{
allPosts {
title
}
}
ちゃんと全件取れていますね!
指定したtitle
のみ返ってきていることも確認出来ました!
mutationで記事作成
続いて記事作成をできるようにしていきましょう。
まずMutationTypeにも記事作成用のfieldを追加します。
module Types
class MutationType < Types::BaseObject
field :create_post, mutation: Mutations::CreatePost
end
end
次にCreatePostクラスを実装しましょう。
module Mutations
class CreatePost < GraphQL::Schema::Mutation
null false
argument :title, String, required: true
argument :content, String, required: true
field :post, Types::PostType, null: false
def resolve(**arg)
post = Post.new(
title: arg[:title],
content: arg[:content]
)
if post.save
{
post: post
}
else
raise GraphQL::ExecutionError, post.errors.full_messages.join(", ")
end
end
end
end
こちらもTypes、Queryと同じくまずfieldを指定する必要があります。
そして実際に処理はresolveメソッドの中で行っていますね。
引数で渡されたtitleとcontentをsaveするだけの単純なものです。
ではこちらもテストクライアントで実行してみましょう。
下記queryを渡してみます。
mutation {
createPost(title: "テスト2", content: "あああ")
{
post {
id
title
}
}
}
こちらも記事作成が確認できました!
しっかりdataも返ってきていますね。
では実際にフロントエンドとの連携部分を作ってきましょう。
フロントエンド実装
最後にフロントの実装をしていきます。
先程作った記事全件取得queryを使って記事一覧ページを作ります。
VueでGraphQLとの連携部分はApollo Clientというモジュールを使います。
必要なものインストールしましょう。
下記ではyarnを使っておりますが、npmでも大丈夫なはずです。
$ yarn add vue-apollo vue-router apollo-client graphql graphql-tag apollo-boost
インストールが出来たらviewを修正します。
<div id="app">
<div>
<router-view></router-view>
</div>
</div>
<%= javascript_pack_tag 'app' %>
erbではこれだけでOKです。
後はVueを書いていきましょう。
まずVueのオブジェクトを作成するapp.jsを作成します。
import Vue from 'vue/dist/vue.esm.js'
import VueRouter from 'vue-router'
import VueApollo from 'vue-apollo'
import ApolloClient from "apollo-boost"
import Index from './components/index.vue'
const apolloClient = new ApolloClient({
uri: "http://localhost:5000/graphql"
});
const apolloProvider = new VueApollo({
defaultClient: apolloClient
})
Vue.use(VueApollo)
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/', component: Index },
],
})
const app = new Vue({
router,
apolloProvider,
el: '#app',
})
次に実際に処理を行うcomponentsを作ります。
<template>
<div>
<ul v-for="post in allPosts">
<li>タイトル: {{post.title}}</li>
<li>内容: {{post.content}}</li>
</ul>
</div>
</template>
<script>
import gql from 'graphql-tag'
const POSTS_QUERY = gql`
query allPosts{
allPosts {
title
content
}
}`
export default {
name: 'index',
data () {
return {
allPosts: []
}
},
apollo: {
allPosts: {
query:POSTS_QUERY
}
}
}
</script>
graphql-tagを使うことでクエリをパースし、GraphQLに渡してくれます。
表示側ではただ記事をforで回して表示しているだけですね。
最後にhome#indexをrootに設定しておきます。
Rails.application.routes.draw do
root to: 'home#index'
では表示されているか確認してみましょう。
ここまで出来たら完成です。
mutationで記事作成機能も作ってあるので、そちらも同じくmutationにqueryを投げる実装をフロント側で作成すれば実行できますね。
まとめ
・GraphQLでRailsを使うことで型システムが使える
・RESTと比べて必要なデータだけ取得できるため、クライアントのスイッチがしやすい
などの強力なメリットを知ることが出来ました。
ですが、GraphQLではeager loadingなどの仕組みが無いようなのでN+1問題が起こる等のデメリットもあります。
サーバー側でどの言語と連携するのが使いやすいのかなどをもう少し調べてみようと思います。
おわりに
@ihsiek を始め、Ateam Lifestyle x cymaアドベントカレンダー運営メンバーのみなさん、
本当にありがとうございました!
優秀なメンバーが他にも記事を書いてくれているので、
Ateam Lifestyle x cyma Advent Calendar 2018から読んでいただけると幸いです。
エイチームグループでは、一緒に働けるチャレンジ精神旺盛な仲間を募集しています。興味を持たれた方はぜひエイチームグループ採用サイトを御覧ください。
https://www.a-tm.co.jp/recruit/
参考
https://qiita.com/vsanna/items/031aa5a17a2f284eb65d
https://qiita.com/naoki85/items/51a8b0f2cbf949d08b11
https://qiita.com/193/items/bf9842af0e1df8fb7226
https://marketing-web.hatenablog.com/entry/vue-apollo_github