Docker Rails7 apiモード mysqlでGraphqlを使用する方法

# 記事の内容
Graphqlをキャッチアップするにあたって、そもそもなんでGraphqlを使用するのか？ からはじめました。
また、学習するにあたって初めて聞くような単語がたくさんあったので、メモがてらこの記事にまとめていこうと思いました。
この記事ではDockerでの環境構築からuserのindex,show,createまでを記載しようと思います。

# Graphqlとは？
さっそくですが[参考](https://udemy.benesse.co.jp/development/system/graphql.html)がとてもわかりやすかったのです。GraphQLを導入した有名なサービスも紹介されており面白いです。
API用のクエリ言語であり、REST APIではサーバ側で返すデータ構造等を決めていたが、実際にデータを使用するクライアント側で自由にデータを取得できます。
今日のスマホやSPAでは複雑なデータを扱うことが多いため、注目されている。

### メリット・デメリット

#### メリット
- フロント側でレスポンスの形式を指定できる
  - 必要なデータのみ取得でき、オーバーフェッチ、アンダーフェッチがなくなる
  - Swagger等の仕様書が必要なくなる
- 型があるため堅牢
- 一度に複数のリソースを取得できる
  - RESTでいう post/1,post/2のようなリソースを一気に取得できる
#### デメリット
- 学習コストがかかる
- キャッシュが REST よりも複雑になる。N+１問題等に気をつける必要あり。

### 単語
- クエリ言語：Graphqlサーバへの問い合わせ内容を記載した言語
- スキーマ言語: Graphqlサーバでのデータの型を記載した言語
- リゾルバ: 問い合わせを実行するメソッド
- Query: SQLでいうSELECTにあたるもの。データを取得する際に使用
- Mutation: SQLでいうINSERT, UPDATE,DELETE。データを作成、変更削除する際に使用
- field: クラスメソッド。データをDBからもってくる単位
- Argument: 検索やデータの呼び出しに使う引数。Queryに渡す引数を指定。


# 実装
### 環境構築

#### 1.各種ファイルを用意
以下ファイルを作成
``` :Dockerfile
FROM ruby:3.1.2


RUN mkdir /app
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install
COPY . /app

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
EXPOSE 3306

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
```

``` :docker-compose.yml
version: '3'
services:
  db:
    platform: linux/x86_64  #M1対応
    image: mysql:5.7
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
      MYSQL_USER: root
      MYSQL_PASSWORD: root
    ports:
      - '3306:3306'
    volumes:
      - ./tmp/db:/var/lib/mysql

  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
    ports:
      - '3000:3000'
    depends_on:
      - db
```

``` :Gemfile
source 'https://rubygems.org'
gem 'rails', '~>7.0.3'
```

##### 空のGemfile.lock

``` :entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /app/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
```

#### 2.Rails new
```
% docker-compose run --rm web rails new . --force --database=mysql --api
```

#### 3.イメージbuild
```
% docker-compose build
```
#### 4.database.ymlの設定
``` :config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: db
```

#### 5.DBのcreate,migration
```
% docker-compose run --rm web rails db:create
Creating rails_backend_web_run ... done
Created database 'app_development'
Created database 'app_test'

% docker-compose run --rm web rails db:migration
```

#### 6.コンテナ起動
```
% docker-compose up -d
```
http://localhost:3000/ にアクセスし、Railsの画面がでればOK

## Graphql
参考
[graphql-rubyでAPIを作成](https://qiita.com/k-penguin-sato/items/07fef2f26fd6339e0e69)
[Rails + GraphQLでAPI作成](https://zenn.dev/slowhand/articles/4fe99377185100)

### 準備
graphqlのインストール

``` :Gemfile
gem 'graphql'    #追加
```

```
% bundle install
% rails generate graphql:install   #様々なファイルが作成されます
```

上記コマンドで記述されているか確認する
```ruby:routes.rb
Rails.application.routes.draw do
  post "/graphql", to: "graphql#execute" # rails generate graphql:installで記述されているか確認
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Defines the root path route ("/")
  # root "articles#index"
end

```

### 動作確認

[Altair GraphQL Client](https://altairgraphql.dev/)をインストールして、下記のようにリクエストを送信しHello World!が表示されればOK。

![スクリーンショット 2023-01-05 9.16.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1874224/9f5b2655-8db8-a054-5627-342bc044498c.png)


### モデル作成

```
% rails g model user
```

```ruby

class CreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string      :name
      t.string      :email

      t.timestamps
    end
  end
end

```
```
% rails db:migrate
```

### 型ファイルの作成
```
% rails g graphql:object User
   create  app/graphql/types/user_type.rb

```

上記コマンドを実行し、先程作成したモデルに合わせて以下ファイルが生成されます。
``` ruby: app/graphql/types/user_type.rb
# frozen_string_literal: true

module Types
  class UserType < Types::BaseObject
    field :id, ID, null: false
    field :name, String
    field :email, String
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
  end
end

```

### Queryの作成
Query: SQLでいうSELECTにあたるもの。データを取得する際に使用。

graphql/type/query_type.rb に下記のように記述します。
``` ruby:graphql/type/query_type.rb
module Types
  class QueryType < Types::BaseObject
    # Add `node(id: ID!) and `nodes(ids: [ID!]!)`
    include GraphQL::Types::Relay::HasNodeField
    include GraphQL::Types::Relay::HasNodesField

    # Add root-level fields here.
    # They will be entry points for queries on your schema.
    field :users, resolver: Resolvers::QueryTypes::UsersResolver # 追加
    # fieeld: クラスメソッド。データをDBからもってくる単位
    # 追加した記述でリゾルバ（問い合わせを実行するメソッド）を実行
  end
end
```
app/graphql/resolvers/query_types/users_resolver.rbに下記のように記述

``` ruby:app/graphql/resolvers/query_types/users_resolver.rb
module Resolvers::QueryTypes
  class UsersResolver < GraphQL::Schema::Resolver
    type [Types::UserType], null: false
    def resolve
      User.all
    end
  end
end
```

画像のような結果になっていればOK（Userのレコードは各自作成してください）

![スクリーンショット 2023-01-05 10.04.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1874224/d872f6a5-b634-0102-ce7f-1a1f0d5ae4ba.png)

### Argumentを使用し、指定ユーザのデータを取得

Argument: 検索やデータの呼び出しに使う引数。Queryに渡す引数を指定。

graphql/type/query_type.rb にfieldを追加。
``` ruby:graphql/type/query_type.rb
module Types
  class QueryType < Types::BaseObject
    include GraphQL::Types::Relay::HasNodeField
    include GraphQL::Types::Relay::HasNodesField

    # Add root-level fields here.
    # They will be entry points for queries on your schema.
    field :users, resolver: Resolvers::QueryTypes::UsersResolver 
    field :user, resolver: Resolvers::QueryTypes::UserResolver # 追加
    # fieeld: クラスメソッド。データをDBからもってくる単位
    # 追加した記述でリゾルバ（問い合わせを実行するメソッド）を実行
  end
end
```

app/graphql/resolvers/query_types/users_resolver.rbファイルを作成し、以下のように記述。

``` ruby:app/graphql/resolvers/query_types/users_resolver.rb
module Resolvers::QueryTypes
  class UserResolver < GraphQL::Schema::Resolver
    type Types::UserType, null: false
    argument :id, ID, required: false # 引数。
# 型はapp/graphql/resolvers/query_types/users_resolver.rbを参照

    def resolve(id:)
      User.find(id)
    end
  end
end

```

以下のようなクエリで指定したIDのユーザ情報を取得
``` :query
query {
  user(id: 1) {
    id
    name
    email
    createdAt #ここに記載したカラムのデータが返ってくる
  }
}
```

![スクリーンショット 2023-01-05 15.00.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1874224/4711a06e-3ba7-ea8f-04c6-648319f7ebc9.png)

また、nameで検索したい場合は以下のように修正

``` ruby:app/graphql/resolvers/query_types/users_resolver.rb
module Resolvers::QueryTypes
  class UserResolver < GraphQL::Schema::Resolver
    type Types::UserType, null: false
    argument :name, String, required: false # 引数。name
# 型はapp/graphql/resolvers/query_types/users_resolver.rbを参照

    def resolve(name:)
      User.find_by(name: name)
    end  
  end
end

```
``` :query
query {
  user(name: "test2") {
    id
    name
    email
    createdAt
  }
}
```
#### 結果

![スクリーンショット 2023-01-05 15.06.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1874224/51c32e09-a823-c9bd-c165-b1e8b6e67b17.png)


### Mutationの作成
Mutation: SQLでいうINSERT, UPDATE,DELETE。データを作成、変更削除する際に使用。
まずはユーザ作成を実装します。

下記コマンドを実行します。
```
% rails g graphql:mutation CreateUser
```
下記ファイルが作成されます。
``` ruby:app/graphql/types/mutation_type.rb
module Types
  class MutationType < Types::BaseObject
    field :create_user, mutation: Mutations::CreateUser
  end
end

```
同じく自動生成された app/graphql/mutations/create_user.rbを下記のように修正。
```ruby: app/graphql/mutations/create_user.rb
module Mutations
  class CreateUser < BaseMutation
    graphql_name 'CreateUser'
    field :user, Types::UserType, null: true

    argument :name, String, required: true # 引数
    argument :email, String, required: true # 引数

    def resolve(**args) # **argsに引数がす
      user = User.create!(args)
      {
        user: user
      }
    end
  end
end

```

以下のmutationを実行
```
mutation {
  createUser(
    input:{
      name: "user"
      email: "user@email.com"
    }
  ){
    user {
      id
      name 
      email
    }
  }
}
```

argsの中身は以下のようになっています
```
[1] pry(#<Mutations::CreateUser>)> args
=> {:name=>"user", :email=>"user@email.com"}

```

#### 結果
![スクリーンショット 2023-01-05 15.41.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1874224/3d51823f-7044-6988-c7be-dd02ae562415.png)

# 終わりに

環境構築から簡単な機能の実装方法をざっとまとめてみました。
今後graphql-railsの設計も学習したいと思います。