Help us understand the problem. What is going on with this article?

Rails APIモードで始めるGraphQL

はじめに

本記事は、社内でGraphQL入門勉強会を実施した際の内容です。
本記事での対象は、

  • graphql-ruby/graphiql-railsの設定
  • Query(データ取得)
  • Mutation(データ更新)

になります。

内部的な仕組みなど、細かい内容は出てきません。
とりあえず動かして雰囲気を掴むことを目的としています。

環境

バージョン
Ruby 2.6.0
Rails 5.2.2
graphql-ruby 1.9.3
graphiql-rails 1.6.0

GraphQLとは

GraphQLは、2012年にFacebookによって開発されたデータクエリ言語および仕様です。
RESTベースのアーキテクチャに代わるものを提供します。
GraphQLはGitHubのAPI(GraphQL API v4)でも採用されています。

GraphQL API v4 (https://developer.github.com/v4/)

GraphQLでは、クエリ言語を使用してデータの取得や更新を行います。

  • query (データ取得系)
  • mutation (データ更新系)
  • subscription (イベント通知) ※本記事では対象外

Preparation

graphql-ruby導入前のサンプルプロジェクトを用意しています。
本記事では、このサンプルプロジェクトにgraphql-rubyを導入していきます。

git clone git@github.com:dkawabata/graphql-ruby-sample.git
cd graphql-ruby-sample
bundle install --path vendor/bundle
bundle exec rails db:migrate
bundle exec rails db:seed

サンプルプロジェクトは、usersテーブル/postsテーブル/commentsテーブルの3テーブルで構成しています。

schema.png

Setup

GraphQL Rubyのインストール

Gemfileを編集します。

Gemfile
+ gem 'graphql'
group :development do
+  gem 'graphiql-rails'
end

graphiqlというブラウザでクエリを実行して、GraphQLの動作確認ができるツールのRails版がgraphiql-railsです。
GraphQLでの開発に必須ではありませんが、今回はgraphiqlで動作確認するため、追加しています。
Gemfileを編集したら、インストールします。

bundle install
bundle exec rails g graphql:install

APIモードでは無い場合、graphiql-railsの設定(Gemfileへの追加や、以降で行うroutes.rbの編集)は不要です。
rails g graphql:installのタイミングで自動的に設定されますが、APIモードの場合はSkipされるようです。

Graphiqlの設定

routes.rbの編集

config/routes.rb
if Rails.env.development?
  mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/graphql'
end

application.rbの編集

Rails APIモードでgraphiqlを利用するために、application.rbrequire "sprockets/railtie"のコメントアウトを外します。
graphiql-railsのREADMEにも記載されています。

https://github.com/rmosolgo/graphiql-rails#note-on-api-mode

If you're using Rails 5 in "API mode", you'll also need to add require "sprockets/railtie" to your application.rb.

config/application.rb
- # require "sprockets/railtie"
+ require "sprockets/railtie"

サンプルクエリの実行

ここまでで一通りの準備は完了です。
rails sでサーバを起動して、http://localhost:3000/graphiqlにアクセスしてみましょう。
graphiqlの画面が表示されます。
左側にクエリを入力して実行すると、右側に結果が表示されます。
下図のように、testFieldというクエリを実行して、"Hello World!"が表示されれば成功です :100:

{62A0459F-9BBB-4CA6-BFC6-551BA6EECE12}.png

追記

Sprockets 4を使うと、Expected to find a manifest file in app/assets/config/manifest.js (Sprockets::Railtie::ManifestNeededError)というエラーが発生します。

app/assets/config/manifest.jsを作成して、下記を記載すると、とりあえずエラーは回避できます。

app/assets/config/manifest.js
//= link graphiql/rails/application.css
//= link graphiql/rails/application.js

https://github.com/rmosolgo/graphiql-rails/issues/75

Query

Queryはデータを取得するために定義します。
Queryを追加する前に、各モデルに対応したTypeを定義しましょう。

型の定義

user/post/commentの各モデルに対応したTypeを定義します。

app/graphql/types/user_type.rb
module Types
  class UserType < Types::BaseObject
    field :id, Int, null: false
    field :name, String, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
    field :posts, [Types::PostType], null: false
    field :comments, [Types::CommentType], null: false
  end
end
app/graphql/types/post_type.rb
module Types
  class PostType < Types::BaseObject
    field :id, Int, null: false
    field :user, Types::UserType, null: false
    field :subject, String, null: false
    field :body, String, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
    field :comments, [Types::CommentType], null: false
  end
end
app/graphql/types/comment_type.rb
module Types
  class CommentType < Types::BaseObject
    field :id, Int, null: false
    field :post, Types::PostType, null: false
    field :user, Types::UserType, null: false
    field :body, String, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
  end
end

graphql-rubyの場合、各Typeに定義しているfieldと同名のメソッド/attributeがモデルに定義されている場合、自動的にデータを取得できます。

fieldで型を指定しますが、標準で用意されている型には以下のようなものがあります。

  • String
  • Int
  • Float
  • Boolean
  • ID
  • ISO8601DateTime

[]で囲うと配列になります。

Queryの追加

それでは、実際にデータを取得するためのQueryを追加しましょう。
特定のidを持つUserを1件返すQueryと、全てのUserを返すQueryを定義してみましょう。
QueryはTypes::QueryTypeに定義を追加します。

app/graphql/types/query_type.rb
module Types
  class QueryType < Types::BaseObject
-    # Add root-level fields here.
-    # They will be entry points for queries on your schema.

-    # TODO: remove me
-    field :test_field, String, null: false,
-      description: "An example field added by the generator"
-    def test_field
-      "Hello World!"
-    end

+    field :user, Types::UserType, null: false do
+      description 'ユーザ情報を1件取得する'
+      argument :id, Int, required: true, description: 'ユーザID'
+    end
+    def user(id:)
+      User.find(id)
+    end

+    field :users, [Types::UserType], null: false, description: 'ユーザ情報を全件取得する'
+    def users
+      User.all
+    end
  end
end

Queryの実行

実際にgraphiqlからQueryを実行してみましょう。
まずは、idを指定して1件取得してみます。

query {
  user(id: 1) {
    id
    name
  }
}

次に、登録されているuser全てを取得します。

query {
  users {
    id
    name
    posts {
      id
      subject
      body
    }
  }
}

上記のQueryでは、各ユーザのidname、そのユーザが投稿したPostを全て取得しています。
Postが不要な場合は、idnameだけ取得するということも可能です。
Queryを実行する側が、必要なデータを選択して取得することができる点は、GraphQLのいい点ですね!

Mutation

次はデータを登録するためのMutationを定義しましょう。
特定のPostにコメントを追加するMutationを定義します。

Mutationクラスの作成

app/graphql/mutations/base_mutation.rb
module Mutations
  class BaseMutation < GraphQL::Schema::RelayClassicMutation
  end
end
app/graphql/mutations/create_comment.rb
module Mutations
  class CreateComment < Mutations::BaseMutation
    argument :user_id, Int, required: true
    argument :post_id, Int, required: true
    argument :body, String, required: true

    field :comment, Types::CommentType, null: true
    field :errors, [String], null: false

    def resolve(user_id:, post_id:, body:)
      comment = Comment.new(user_id: user_id, post_id: post_id, body: body)

      if comment.save
        {
          comment: comment,
          errors: []
        }
      else
        {
          comment: nil,
          errors: comment.errors.full_messages
        }
      end
    end
  end
end

Mutation定義の追加

先ほど作成したCreateCommentを使えるようにするため、Mutationの定義を追加します。

app/graphql/types/mutation_type.rb
module Types
  class MutationType < Types::BaseObject
    field :create_comment, mutation: Mutations::CreateComment
  end
end

Mutationの実行

では、graphiqlからMutationを実行してみましょう。

mutation {
  createComment(input: {
    userId: 1
    postId: 1
    body: "コメント追加"
  })
  {
    comment {
      id
      body
    }
  }
}
{
  comment {
    id
    body
  }
}

の部分は、登録したcommentの内容を返却するための記述です。

まとめ

ざっくりですが、RailsのプロジェクトにgraphQLを導入して、QueryMutationを定義する部分を行いました。
一部古い情報もありますが、graphql-rubyのガイド( https://graphql-ruby.org/guides )を見ると、色々載っているので参考になります。

参考URL

https://github.com/rmosolgo/graphql-ruby
https://github.com/rmosolgo/graphiql-rails
https://graphql-ruby.org/guides

pure-system
大手生命保険会社各社をはじめ、自治体、大手企業など、日本を代表する企業様のIT化を推進・支援しています。
https://www.pure-system.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした