Rails × GraphQL でAPI開発｜導入からCRUDまでの実践ガイド

# 概要

本記事は `Rails` × `GraphQL`の構成で開発を行う方に向けて執筆しています。

この記事では、`Rails` アプリケーションにおける `GraphQL` の導入方法や、DB操作方法を実際のコード例を交えて紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842674/15ba741a-ee18-430f-b081-20f4488d572d.png)



記事の中で扱うライブラリのバージョンについては以下の通りです。

- Rails `7.1.5.1`
- GraphQL `2.5.3`

# Graph QLとは？

`GraphQL` は、Facebookが2015年に開発・オープンソース化したAPI向けのクエリ言語およびランタイムです。

従来の `REST API` とは異なる設計思想を持ち、クライアントが「必要なデータのみ」を取得できる柔軟性が特徴です。
- 1つのエンドポイントで様々なデータ取得が可能
- オーバーフェッチングの防止
- 型指定が明確

## REST API vs GraphQL

GraphQLとREST APIの主な違いを簡単に比較してみます。

| 特徴 | REST API | GraphQL |
|------|----------|---------|
| エンドポイント | 複数エンドポイント | 単一エンドポイント |
| 取得データ | 固定の構造 | クライアントが必要なデータを指定 |
| バージョニング | 明示的なバージョン管理 | スキーマ拡張による暗黙的管理 |
| 習得難易度 | 比較的容易 | 高め |
| エラーハンドリング | HTTPステータスコード | すべて200で返される<br>（レスポンス内でエラー情報提供） |

# セットアップ

`GraphQL` の概要を理解したところで、早速開発環境を構築していきましょう。
既存の`Rails`プロジェクトに `Graph QL`を導入していきます。

## 1. Gemの追加、bundle install

以下を`Gemfile`に追加します。
```ruby
gem 'graphql'
gem "graphiql-rails", group: :development
```

追加が完了したら、`bundle install` を実行します。

```ruby
bundle install
```

## 2. ジェネレーターを実行
次に`GraphQL`で利用する設定ファイルを作成します。
ジェネレーターが用意されているので、コマンドを実行します。

```
rails g graphql:install
```

ジェネレーターを実行すると、Graph QLで必要となるファイルが複数作成されます。
```ruby
eate  app/graphql/types
create  app/graphql/types/.keep
create  app/graphql/myapp_schema.rb
create  app/graphql/types/base_object.rb
create  app/graphql/types/base_argument.rb
create  app/graphql/types/base_field.rb
create  app/graphql/types/base_enum.rb
create  app/graphql/types/base_input_object.rb
create  app/graphql/types/base_interface.rb
create  app/graphql/types/base_scalar.rb
create  app/graphql/types/base_union.rb
create  app/graphql/resolvers/base_resolver.rb
create  app/graphql/types/query_type.rb
add_root_type  query
create  app/graphql/mutations
create  app/graphql/mutations/.keep
create  app/graphql/mutations/base_mutation.rb
create  app/graphql/types/mutation_type.rb
add_root_type  mutation
create  app/controllers/graphql_controller.rb
  route  post "/graphql", to: "graphql#execute"
  route  graphiql-rails
create  app/graphql/types/node_type.rb
insert  app/graphql/types/query_type.rb
create  app/graphql/types/base_connection.rb
create  app/graphql/types/base_edge.rb
insert  app/graphql/types/base_object.rb
insert  app/graphql/types/base_object.rb
insert  app/graphql/types/base_union.rb
insert  app/graphql/types/base_union.rb
insert  app/graphql/types/base_interface.rb
insert  app/graphql/types/base_interface.rb
insert  app/graphql/myapp_schema.rb
insert  config/application.rb
```

以上でセットアップは完了です。
お疲れ様でした👌

# GraphiQL

本セクションでは`GraphiQL`について解説します。

## GraphiQLとは？
`GraphQL`を検証するためのGUIクライアントです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842674/ea72bee3-c4d0-4609-9022-ccaf64049240.png)

Rails環境では、`graphiql-rails` をインストールすることで、http://localhost:3000/graphiql にアクセスすることで利用することができます。

※ホスト名は任意の値に置き換えてください。


本セクションでも`GraphiQL`を使用して`GraphQL`の検証を行なっていきます。

# ActiveRecordからデータを取得する

`GraphQL`を用いて、`ActiveRecord`のデータを取得する方法について解説します。
ここでは User モデルを例に、単体のデータ取得・一覧取得・カスタムフィールドの扱いなど、基本的なクエリの実装パターンを紹介します。

使用する`User`テーブルのスキーマは以下の通りです。

```
MySQL [myapp_development]> desc users;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | bigint       | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255) | NO   |     | NULL    |                |
| email      | varchar(255) | NO   | UNI | NULL    |                |
| password   | varchar(255) | NO   |     | NULL    |                |
| created_at | datetime(6)  | NO   |     | NULL    |                |
| updated_at | datetime(6)  | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
```

以降の検証では、上記`User` モデルを使用するため、ハンズオンで進めたい方はモデルを作成してください。

```bash
rails g model User name:string email:string:uniq password:string
```

# [事前準備] Typeファイルの作成

データ取得の事前準備として、GraphQL上で使用する型を定義します。
GraphQLでは、レスポンスとしてどのようなデータを返すのかを、あらかじめ定義しておく必要があります。

Railsでは、以下のジェネレーターを使って型定義を自動生成することができます。

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

生成された `app/graphql/types/user_type.rb` をみていきましょう。
スキーマ情報から型定義が自動作成されていますね🙋‍♂️

```ruby
module Types
  class UserType < Types::BaseObject
    description "User Model"

    field :id, ID, null: false
    field :name, String, null: false
    field :email, String, null: false
    field :password, String, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
  end
end
```

### 解説
詳細を見ていきます。

#### 🔍 `field`とは？

GraphQLでレスポンスとして返す項目（カラムやメソッド）を定義するものです。  
上記の例で言うと、`id`, `name`, `email` などがすべて「フィールド」に該当します。

構文は以下のようになります：

```bash
field :field_name, type, null: true|false
```

たとえば、以下の場合は「nameというフィールドに、文字列（String）型の値を返す。nullは許容しない」という意味になります。

```ruby
field :name, String, null: false
```

実際のデータが`nil`だった場合、**GraphQLはエラーとしてレスポンスを返します。**

```javascript
{
  "errors": [
    {
      "message": "Cannot return null for non-nullable field User.email",
      //...
    }
  ]
}
```

#### 🧱 Scalar型とは？

GraphQLでは各フィールドにScalar型（`String`, `Int`, `Boolean`...）を設定します。 
これは、数字や文字列など、基本的なデータ型を表すものです。

| GraphQL型 | 説明           |
|-----------|----------------|
| `ID`      | 識別子         |
| `String`  | 文字列         |
| `Int`     | 整数           |
| `Float`   | 浮動小数点数   |
| `Boolean` | true / false   |
| `GraphQL::Types::ISO8601DateTime`| ISO 8601形式の日付・時刻     |

Rubyの `graphql-ruby` ライブラリでは、日付やカスタム型なども扱えます。詳細は以下の公式ドキュメントをご覧ください：  

https://graphql-ruby.org/type_definitions/scalars.html

# [Read] 単一データの取得

事前準備が完了したところで、早速データを取得していきましょう。
本セクションでは特定のユーザーデータ1件を取得する処理を実装していきます。

## 1. エンドポイントの定義
最初に、`User` データを取得するためのエンドポイントを定義します。
この処理は、`query_type.rb` に記述します。

`query_type.rb`は、GraphQLスキーマにおけるクエリのエントリーポイントとして機能し、クライアントからのデータ取得リクエストを受け付ける役割を担います。

```ruby
module Types
  class QueryType < Types::BaseObject
    # ...
    
    field :user, Types::UserType, null: true, description: "ユーザー" do
      argument :id, ID, required: true
    end

    def user(id:)
      User.find(id)
    end
  end
end
```

### 解説

#### 🔍  `field`
- クライアントが呼び出すクエリ名（ここでは `user`）を定義します
- このクエリが返すデータの型（`Types::UserType`）と、`null` を許容するかどうかを指定します
- `description`にはAPIドキュメントとして表示される説明文を設定できます

#### 🛠️ `argument`
- クエリに渡す 引数（パラメータ） を定義します
- `id: ID, required: true` は、「IDという名前の引数を必須で受け取る」という意味です

#### 🔑 `def user(id:)`
- クエリが実行された際に呼び出される**リゾルバ関数**です
- 実際にデータベースから `User` レコードを取得する処理をここに記述します

## 動作確認

動作検証のために、テスト用のレコードを1件作成しておきましょう。

```ruby
User.create!(id: 1, email: "test@example.com", name: "yamada", password: "password")
```

`GraphiQL`（http://localhost:3000/graphiql） を使って、以下クエリを実行します。

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

クエリを実行すると、指定したフィールド（`name`, `email`）のみがレスポンスとして返されます。

`GraphQL`ではこのように **必要な項目を明示的に指定する** ことで、不要なデータを取得してしまう「**オーバーフェッチング**」を防ぐことができます👌

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842674/5a84b2e3-0c6b-4df1-ac63-70c6fb2a7277.png)

# [Read] 複数データの取得

次に複数のUserレコードを取得していきましょう。

GraphQLでは、単一のデータだけでなく、複数のデータも簡単に取得できます。
早速進めていきましょう。

## 1. エンドポイントの定義

`query_type.rb` に新しいフィールドを追加します。

```ruby
module Types
  class QueryType < Types::BaseObject
    # ...

    # --- 追加 ---
    field :users, [Types::UserType], null: true, description: "ユーザー一覧"
    # --- ここまで ---

    def users
      User.all
    end
  end
end
```

### 解説
#### 🧳 `[Types::UserType]`
- GraphQLでは、配列を定義する場合、`[Type]` という形式を使います。
- 今回のケースでは、複数のユーザー情報を取得したいので`[Types::UserType]` としています。

## 動作確認

動作検証のために、テスト用のレコードを複数件作成しておきましょう。

```ruby
User.create!(id: 2, email: "test2@example.com", name: "sato", password: "password")
User.create!(id: 3, email: "test3@example.com", name: "suzuki", password: "password")
```

`GraphiQL`（http://localhost:3000/graphiql） で以下のクエリを実行します。
このクエリは、複数のユーザーの `name` と `email` を取得します。

```graphql
{
  users {
    name
    email
  }
}
```

クエリを実行すると、レスポンスとして、指定された `name` と `email` の情報を持つ複数のユーザーが返されます。

このようにすることで簡単に複数のレコードを取得することができますね👌

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842674/1d5d0682-b016-4d8d-951f-197c9635623c.png)


# [Create] Userデータの作成

次にユーザーの作成処理を実装します。

GraphQLでは、データを作成、更新、削除する操作を「**`Mutation`（ミューテーション）**」と呼びます。

データ作成処理の `Mutation` を実装していきましょう。

## 1. UserType の編集

まずはクライアントから受け付けるデータのスキーマを定義します。
` types/user_input_type.rb` を新しく作成します。

```ruby
module Types
  class UserInputType < Types::BaseInputObject
    graphql_name "UserInputType"
    description "User input for creating a new user"

    argument :name, String, required: true
    argument :email, String, required: true
    argument :password, String, required: true
  end
end

```

## 2. Mutation の作成

次に、`app/graphql/mutations/create_user.rb` を作成します。

```ruby
class Mutations::CreateUser < GraphQL::Schema::Mutation
  null true

  argument :user, Types::UserInputType, required: true

  def resolve(user:)
    User.create(user.to_h)
  end
end
```

### 解説

#### 🧸 `argument`
- `Mutation` に渡す引数を定義

#### 📂 `resolve`
- データを操作する処理を記述

## 3. エンドポイントの定義

最後にエンドポイントを定義します。

`mutation_type.rb` は、**ミューテーションのエントリーポイント** として機能します。
クライアントからのデータ操作リクエスト（作成・更新・削除）を受け付ける役割を担います。

`mutation:` には、前段で作成したミューテーション（`Mutations::CreateUser`） を指定します。

```ruby
module Types
  class MutationType < Types::BaseObject
    # --- 追加 ---
    field :create_user, Types::UserType, mutation: Mutations::CreateUser
    # --- ここまで ---
  end
end
```

### Variables
動作確認の前に、クエリで変数を使う方法を見ていきましょう。

`GraphQL` では、クエリに変数（`Variables`）を渡すことができます。
これにより、動的な値をクエリに渡すことができ、同じクエリ構造で異なるデータを操作することが可能になります。

変数を使う基本的な構文は次のとおりです。
1. クエリやミューテーションの冒頭で変数を宣言
`mutation CreateUser($user: UserInputType!)`
1. クエリ内で変数を参照
`createUser(user: $user)`
1. 変数を渡してクエリを実行する
`GraphiQL`では、`Variables`ペインで`JSON`形式の値を設定

## 動作確認
それでは動作確認を行います。

### Query
```graphql
mutation createUser($user: UserInputType!) {
  createUser(user: $user) {
    id
  }
}
```

### Variables
```json
{
  "user": {
    "name": "honda",
    "email": "honda@gmail.com",
    "password": "password"
  }
}
```

クエリを実行すると、作成されたレコードの `id` の情報が返されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842674/a5113f66-dbd2-4a41-8b3d-7e3ea1cdf8d2.png)

コンソール上でも実際にデータが作成されていることを確認できますね👌

```ruby
irb(main):002> User.find(7)
  User Load (5.7ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 7 LIMIT 1 /*application='Myapp'*/
=> 
#<User:0x0000ffffb8c4bba8
 id: 7,
 name: "honda",
 email: "honda@gmail.com",
 password: "[FILTERED]",
 created_at: Sat, 19 Apr 2025 01:33:48.016869000 UTC +00:00,
 updated_at: Sat, 19 Apr 2025 01:33:48.016869000 UTC +00:00>
```

# [Update] Userデータの更新

ユーザーの作成が完了したところで、ユーザーデータを更新する機能を実装していきます。

## 1. `UserInputType`に`ID`フィールドを追加
まず、更新対象のユーザーを識別するため `UserInputType`に `ID` 引数を追加します。

`ID`があれば更新、 なければ作成としたいので、`required` は `false` とします。

```ruby
module Types
  class UserInputType < Types::BaseInputObject
    graphql_name "UserInputType"
    description "User input for creating a new user"

    # --- 追加 ---
    argument :id, ID, required: false
    # --- ここまで ---
    argument :name, String, required: true
    argument :email, String, required: true
    argument :password, String, required: true
  end
end
```

## 2. Mutation の作成
次に、 `app/graphql/mutations/update_user.rb` を作成します。

ここでは`ID`で指定されたユーザーを検索し、渡された情報でユーザーを更新します。

```ruby
class Mutations::UpdateUser < GraphQL::Schema::Mutation
  argument :user, Types::UserInputType, required: true

  def resolve(user:)
    target_user = User.find(user.id)
    target_user.update!(user.to_h)
    target_user
  end
end
```

## 3. エンドポイントの定義
`mutation_type.rb` に新しいフィールドを追加して、作成したミューテーションを登録します。

```ruby
module Types
  class MutationType < Types::BaseObject
    field :create_user, Types::UserType, mutation: Mutations::CreateUser

    # --- 追加 ---
    field :update_user, Types::UserType, mutation: Mutations::UpdateUser
    # --- ここまで ---
  end
end
```

## 動作確認

### Query
```graphql
mutation updateUser($user: UserInputType!) {
  updateUser(user: $user) {
    id
    name
    email
    password
  }
}
```

### Variables
```
{
  "user": {
    "id": 1,
    "name": "name_updated",
    "email": "updated@gmail.com",
    "password": "password_updated"
  }
}
```

クエリを実行すると、更新されたレコードの情報が返されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842674/c6e15aef-fbbb-4c5c-bc81-1273be64fee7.png)

コンソール上でもデータが更新されていることを確認できました🤟

```ruby
irb(main):001> User.first
  User Load (0.5ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1 /*application:Myapp*/
=> 
#<User:0x0000ffff7f395dd0
 id: 1,
 name: "name_updated",
 email: "updated@gmail.com",
 password: "[FILTERED]",
 created_at: Sun, 20 Apr 2025 10:35:07.309258000 UTC +00:00,
 updated_at: Sun, 20 Apr 2025 11:08:15.543580000 UTC +00:00>
```

# [Destroy] Userデータの削除

最後に、ユーザーデータの削除処理を実装します。
削除操作も他の更新操作と同様に、`Mutation` として実装します。

## 1. Mutation の作成

次に、 `app/graphql/mutations/destroy_user.rb`  を作成します。

ここではIDで指定されたユーザーを検索し、ユーザーを削除します。

```ruby
class Mutations::DestroyUser < GraphQL::Schema::Mutation
  argument :id, ID, required: true

  def resolve(id:)
    target_user = User.find(id)
    target_user.destroy

    target_user.destroyed?
  end
end
```

## 2. エンドポイントの定義
`mutation_type.rb` に新しいフィールドを追加して、作成したミューテーションを登録します。

```ruby
module Types
  class MutationType < Types::BaseObject
    field :create_user, Types::UserType, mutation: Mutations::CreateUser
    field :update_user, Types::UserType, mutation: Mutations::UpdateUser
    # --- 追加 ---
    field :destroy_user, GraphQL::Types::Boolean, mutation: Mutations::DestroyUser
    # --- ここまで ---
  end
end
```

## 動作確認
動作確認をしていきます。

### Query
```graphql
mutation DestroyUser{
  destroyUser(id: "1")
}
```

クエリを実行します。
`"destroyUser": true` が返されているので、削除が成功していそうですね！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842674/65d54615-1962-4a7c-875e-face7ede5400.png)

コンソール上でもデータが削除されていることを確認できました✌️

```ruby
irb(main):002> User.find_by(id: 1)
  User Load (4.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 /*application:Myapp*/
=> nil
```


# まとめ

今回はUserモデルを用いた基本的なCRUD操作について解説しました。

この他にもバリデーションや認可など、考慮すべき項目がいくつかあるので、それらについては別途まとめていく予定です😎

## 追記:

ログイン認証方法に関する記事を公開しました。

https://qiita.com/PenPe/items/314dc80e989a46429b4e