28
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Ateam Lifestyle x cymaAdvent Calendar 2018

Day 25

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

Last updated at Posted at 2018-12-24

はじめに

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

28
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?