Ruby
JavaScript
Rails
Vue.js
GraphQL

Vue×Apollo×Railsを使ってGraphQLを学んでみた


はじめに

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を生成されました。

少し編集します。


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などを追記しておきます。


Gemfile

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


config/database.yml

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が追加されているのが確認できます。


config/routes.rb

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

にアクセスしてみましょう。

スクリーンショット 2018-12-21 10.54.30.png

では実際にGraphQLを実装していきましょう。


queryとmutation

実際にテストクライアントでリクエストを送ってみましょう。

そのまえにGraphQLでは、

データの「読み」の場合は、query。

データの「作成」「更新」「削除」などの場合は、mutationを使います。


queryで記事全件取得

まずはGraphQLのPostオブジェクトにアクセスするためfieldを作成します。


app/graphql/types/post_type.rb

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を追加しましょう。


app/graphql/types/query_type.rb

module Types

class QueryType < Types::BaseObject
field :all_posts, [PostType], null: false
def all_posts()
Post.all
end
end
end

引数も何も渡さず、ただPost.allしているだけですね。

そして、このままだとCSRFトークンが無いと怒られちゃうので、今回は特別にトークン判定をSkipするようにしておきましょう。


app/controllers/graphql_controller.rb

class GraphqlController < ApplicationController

skip_before_action :verify_authenticity_token
...

次に確認のために事前にrailsコンソールなどからダミーデータを作っておきましょう。

ダミーデータを作ったら下記のqueryをテストクライアントで渡してみます。

{

allPosts {
title
}
}

スクリーンショット 2018-12-21 11.51.45.png

ちゃんと全件取れていますね!

指定したtitleのみ返ってきていることも確認出来ました!


mutationで記事作成

続いて記事作成をできるようにしていきましょう。

まずMutationTypeにも記事作成用のfieldを追加します。


app/graphql/type/mutation_type.rb

module Types

class MutationType < Types::BaseObject
field :create_post, mutation: Mutations::CreatePost
end
end

次にCreatePostクラスを実装しましょう。


app/graphql/mutations/create_post.rb

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
}
}
}

スクリーンショット 2018-12-21 12.19.55.png

こちらも記事作成が確認できました!

しっかりdataも返ってきていますね。

では実際にフロントエンドとの連携部分を作ってきましょう。


フロントエンド実装

最後にフロントの実装をしていきます。

先程作った記事全件取得queryを使って記事一覧ページを作ります。

VueでGraphQLとの連携部分はApollo Clientというモジュールを使います。

必要なものインストールしましょう。

下記ではyarnを使っておりますが、npmでも大丈夫なはずです。

$ yarn add vue-apollo vue-router apollo-client graphql graphql-tag apollo-boost

インストールが出来たらviewを修正します。


app/view/home/index.html.erb

<div id="app">

<div>
<router-view></router-view>
</div>
</div>

<%= javascript_pack_tag 'app' %>


erbではこれだけでOKです。

後はVueを書いていきましょう。

まずVueのオブジェクトを作成するapp.jsを作成します。


app/javascript/packs/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を作ります。


app/javascript/packs/components/index.vue

<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に設定しておきます。


config/routes.rb

Rails.application.routes.draw do

root to: 'home#index'

では表示されているか確認してみましょう。

スクリーンショット 2018-12-23 4.51.26.png

ここまで出来たら完成です。

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