GraphQLでRailsのスキーマからフロント(TS, urql)の型生成までできるようにしてみた


## GraphQLとは何か

> GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

*GraphQLは、APIのクエリ言語であり、既存のデータを使用してこれらのクエリを実行するためのランタイムです。GraphQLは、API内のデータの完全でわかりやすい説明を提供し、クライアントが必要なものだけを正確に要求できるようにし、時間をかけてAPIを進化させやすくし、強力な開発者ツールを有効にします。*
引用: https://graphql.org/

要するに、クエリ言語でAPIのドキュメント、問い合わせを定義することで我々が開発しているAPIの変更を容易にしてくれるものです

## GraphQLでできる事

- graphqlドキュメントを介してフロント・バック間の分業がしやすくなる
- バックエンド
    - DBのスキーマからgraphqlのAPI定義(ドキュメント)を自動生成できる
- フロントエンド
    - バックエンドで自動生成したドキュメントからさらに自動で型生成することができる

→******************スキーマ変更を伴う改修をした時の実装コストが下がる！！******************(かもしれない


## ハンズオン

ユーザーを取得するAPIを実装するケースを考えます

### バックエンド

登場人物

- graphql-ruby
- graphiql-rails

前提として、graphqlのAPI開発に必要なファイル群を自動生成します

```bash
rails g graphql:install
```

実際のAPI開発に進みます。今回はUserを取得するエンドポイントを考えます

まず User の オブジェクトを生成します

```bash
rails g graphql:object User
```

するとusersテーブルのスキーマからGraphQL Type相当のコードが自動生成されます。
<details><summary>生成されたコードを見る</summary><div>

```.ruby
# frozen_string_literal: true

module Types
  class UserType < Types::BaseObject
    field :id, ID, null: false
    field :email, String, null: false
    # こういう露出したくないフィールドは削る 
    # field :crypted_password, String
    # field :salt, String
    field :is_verified, Boolean, null: false
    field :deleted_at, GraphQL::Types::ISO8601DateTime
    field :name_sei, String, null: false
    field :name_mid, String, null: false
    field :name_mei, String, null: false
		...
    # associationは自分で追加してあげる必要がある(要N+１対策
    field :activities, [Types::ActivitiyType], null: false
  end
end
```
</div></details>

graphql/query_type.rb でエンドポイントを定義します

```.ruby
field :users, [Types::UserType], null: false do
  description '全ユーザー返す'
end

def users
  User.all
end
```

次に graphql スキーマを吐かせないといけないのですが、標準でそのような rakeタスクは用意されていないので、自分で作る必要があります。

```lib/tasks/graphql.rake
require "graphql/rake_task"
GraphQL::RakeTask.new(schema_name: "MySchema")
```

このrakeタスクを作成してから

```bash
$ rails graphql:schema:idl
```

を叩くと graphqlドキュメントが生成されます
<details><summary>こちら</summary><div>

```graphql
type Query {
  """
  全てユーザー返す
  """
  users: [User!]!
}

type User {
  createdAt: ISO8601DateTime!
  deletedAt: ISO8601DateTime
  email: String!
  id: ID!
  nameSei: String!
  nameMid: String!
  nameMei: String!
  updatedAt: ISO8601DateTime!
  ...
}
```
</div></details>
APIはこれで OK

### フロントエンド

登場人物

- urql
- graphql-codegen

まずはクエリを定義します。

```tsx
export const UsersQuery = graphql(/* GraphQL query */ `
  query UserQuery {
    users {
      id,
      email,
      nameSei,
      nameMid,
      nameMei
    }
  }
`);
```

次にgraphqlの設定ファイルを置きます

```codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli'

const config: CodegenConfig = {
   schema: './schema.graphql',
   documents: ['src/**/*.{ts,tsx}'],
   generates: {
      './src/graphql': {
        preset: 'client',
        plugins: []
      }
   }
}
export default config
```
これで

```bash
$ yarn graphql-codegen
```

すると型が自動生成されます。(自動生成された型は膨大なので割愛

実際に urql で呼び出すといい感じに型がついています
![%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88_2022-10-26_15.24.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/644662/6b764ba7-b44a-9b61-a950-77b8899bb405.png)
## N+1対策

本筋とは逸れますが、railsでgraphqlを使う場合、`includes` `preload`してもはN+1が起きてしまいます。
そこで、graphql-batchというライブラリを利用することでN＋1を回避することができます。
以下の記事がとても参考になったので供養

https://jpdebug.com/p/3144171

## 参照

- https://graphql.org/
- https://graphql-ruby.org/
- https://github.com/rmosolgo/graphiql-rails
- https://formidable.com/open-source/urql/
- https://the-guild.dev/graphql/codegen/docs/getting-started