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/)](https://developer.github.com/v4/)

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

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

# Preparation

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

```bash
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](https://qiita-image-store.s3.amazonaws.com/0/161900/0f6239ef-e217-7531-1901-d1e5e2646e32.png)


# Setup
## GraphQL Rubyのインストール

Gemfileを編集します。

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

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

```bash
bundle install
bundle exec rails g graphql:install
```

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

## Graphiqlの設定

### routes.rbの編集

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

### application.rbの編集

Rails APIモードでgraphiqlを利用するために、`application.rb`の`require "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.


```diff: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](https://qiita-image-store.s3.amazonaws.com/0/161900/454608d3-2ed6-be10-75ee-cb1fe512a99d.png)

### 追記

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

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

```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を定義します。

```ruby: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
```

```ruby: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
```

```ruby: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`に定義を追加します。

```diff: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を指定して１件取得してみます。

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

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

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

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

# Mutation

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

## Mutationクラスの作成

```ruby:app/graphql/mutations/base_mutation.rb
module Mutations
  class BaseMutation < GraphQL::Schema::RelayClassicMutation
  end
end
```

```ruby: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の定義を追加します。

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

## Mutationの実行

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

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

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

# まとめ

ざっくりですが、RailsのプロジェクトにgraphQLを導入して、`Query`と`Mutation`を定義する部分を行いました。
一部古い情報もありますが、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