[Rails]GraphQLを導入する
はじめに
RailsアプリにGragphQLを導入する時のセットアップについてまとめてみました。
GraphQL
GraphQLは、クライアントとサーバー間のデータクエリと操作のためのクエリ言語です。
GraphQLの基本的なコンセプトは以下になります。
- スキーマ定義:
GraphQLの最初のステップは、データ構造を定義するスキーマを作成することです。これは、GraphQLサーバーがクエリを理解し、クライアントに返すデータの形式を定義します。
type Query {
hello: String
}
- データ取得のクエリ:
クライアントは、GraphQLクエリを使用してデータを取得します。クエリは、サーバーに要求されたデータの形式を指定します。
query {
hello
}
- サーバー側の処理:
サーバーは、クエリを受け取り、それに対応するデータを取得するためのリゾルバ関数を実行します。
class QueryType < GraphQL::Schema::Object
field :hello, String, null: false
def hello
'world'
end
end
リゾルバ
リゾルバは、GraphQLスキーマ内の特定のフィールドに対するデータの取得や操作を行うためのメソッドです。GraphQLスキーマ内で定義された各フィールドに対して1つずつ存在します。
クライアントが特定のフィールドを要求した場合、対応するレゾルバが呼び出され、データベースクエリの実行や外部APIとの通信など、必要な処理を行いそのフィールドに対するデータを返します。
- データベースクエリの実行: データベースから必要な情報を取得するためのクエリを実行します。これには、ActiveRecordやSQLクエリを使用する場合があります。
def resolve
# データベースからユーザー情報を取得するクエリを実行する例
User.find(args[:id])
end
- 外部APIとの通信: 外部のWebサービスやマイクロサービスからデータを取得するためのHTTPリクエストを送信します。この場合、通常はHTTPクライアントライブラリ(例: Faraday、HTTParty)を使用します。
def resolve
# 外部APIからユーザー情報を取得するHTTPリクエストを送信する例
response = HTTParty.get("https://api.example.com/users/#{args[:id]}")
JSON.parse(response.body)
end
- ビジネスロジックの実行: データの加工や計算、認証や権限のチェックなど、ビジネスロジックを実行することもあります。
def resolve
# ユーザーが特定の権限を持っているかどうかをチェックする例
if context[:current_user].admin?
# 管理者の場合はすべてのユーザーを取得する
User.all
else
# 一般ユーザーの場合は自分の情報のみを返す
context[:current_user]
end
end
- レスポンス:
サーバーは、クライアントに対して要求されたデータを含むレスポンスを返します。
{
"data": {
"hello": "world"
}
}
まとめると、クライアントがGraphQLクエリを送信し、サーバーがそのクエリを処理し、要求されたデータを返すプロセスが行われます。
GraphQLとREST APIの主な違い
-
データの取得方法:
- GraphQL:クライアントが必要なデータの構造をクエリで指定できます。クライアントは1つのクエリで複数のリソースを取得し、不要なデータを取得することなく必要なデータのみを取得できます。
- REST API:固定されたエンドポイントが特定のデータリソースを返します。これにより、必要なデータを取得するために複数のエンドポイントをリクエストする場合があります。
-
データの取得粒度:
- GraphQL:クライアントが必要なフィールドのみを取得できるため、オーバーフェッチングやアンダーフェッチングの問題が解消されます。
- REST API:エンドポイントごとに固定されたデータの形式を提供するため、クライアントはリソースごとに固定されたデータを取得することになります。
-
バージョン管理:
- GraphQL:スキーマのバージョン管理を通じて、新しいフィールドやタイプを追加し、古いフィールドを廃止することができます。
- REST API:通常、エンドポイントのバージョン管理を使用して、APIの変更を管理します。
-
ネットワークトラフィック:
- GraphQL:1つのクエリで複数のリソースを取得できるため、ネットワークトラフィックが削減されます。
- REST API:複数のエンドポイントをリクエストするため、ネットワークトラフィックが増加する場合があります。
-
キャッシュ:
- GraphQL:クエリの結果をキャッシュすることが困難です。クエリは常にユニークなため、キャッシュの再利用が難しい場合があります。
- REST API:特定のエンドポイントからのレスポンスはキャッシュされるため、再利用が容易です。
tl;dr
- Docker+Rails+postgresqlで環境構築
- graphqlをインストールする
- モデルを作成する
- typeを作成する
- QueryTypeを定義する
- 検証
Railsアプリを作成し、GraphQLを通してサーバーからユーザーを取得するまでやっていきます。
Docker+Rails+postgresqlで環境構築
Dockerfile
FROM ruby:3.1.2-alpine3.15
ENV LANG="ja_JP.UTF-8"
ENV app="/app"
WORKDIR $app
RUN apk update \
&& apk upgrade \
&& apk --update add \
g++ \
make \
tzdata \
postgresql-dev \
openssh \
git \
bash \
&& cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
&& rm -rf /var/cache/apk/*
RUN gem update --system \
&& gem install bundler --no-document
EXPOSE 3000
docker-compose.yml
version: '3.8'
services:
db:
image: postgres:15.3-alpine3.18
environment:
- TZ=Asia/Tokyo
volumes:
- db-data:/var/lib/postgresql/data
ports:
- 5432:5432
env_file:
- .env
web:
build:
context: .
environment:
- TZ=Asia/Tokyo
- BUNDLE_APP_CONFIG=/app/.bundle
env_file:
- .env
ports:
- 3000:3000
volumes:
- .:/app
- bundle-data:/usr/local/bundle
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -b 0.0.0.0 -p 3000 -e development"
stdin_open: true
tty: true
depends_on:
- db
volumes:
db-data:
driver: local
bundle-data:
driver: local
.env
# DB
POSTGRES_USER=user
POSTGRES_PASSWORD=password
docker-compose build
を実行します。
docker-compose build
➜ app docker-compose build
[+] Building 41.0s (8/8) FINISHED
=> [web internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 441B 0.0s
=> [web internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [web internal] load metadata for docker.io/library/ruby:3.1.2-alpine3 5.4s
=> [web 1/4] FROM docker.io/library/ruby:3.1.2-alpine3.15@sha256:8a8cba1 6.5s
=> => resolve docker.io/library/ruby:3.1.2-alpine3.15@sha256:8a8cba16117 0.0s
=> => sha256:9621f1afde84053b2f9b6ff34fc7f7460712247c01c 2.82MB / 2.82MB 0.9s
=> => sha256:21fafba7b6adda1d48ec07e3047626a3c4f4340db51 3.69MB / 3.69MB 0.7s
=> => sha256:33a3439ad63dfb217b7df7acc43ed7bca7e070f0b3dbef3 223B / 223B 0.7s
=> => sha256:8a8cba161179095feea42c585711f3a02624737b0ed 1.65kB / 1.65kB 0.0s
=> => sha256:f7d8374ee7b8c959ed6334101c366c1e491037c6e4a 1.36kB / 1.36kB 0.0s
=> => sha256:5f7763e7849218c95267172c4dd712142aa0e588c35 6.10kB / 6.10kB 0.0s
=> => sha256:e14d080185efc14b694993705b4c2aa793338873e 29.53MB / 29.53MB 3.9s
=> => sha256:caae273acc14b6604dd1272fdc3ec3a136e2a1f0a496d4e 173B / 173B 1.3s
=> => extracting sha256:9621f1afde84053b2f9b6ff34fc7f7460712247c01cbab48 0.1s
=> => extracting sha256:21fafba7b6adda1d48ec07e3047626a3c4f4340db51965fa 0.6s
=> => extracting sha256:33a3439ad63dfb217b7df7acc43ed7bca7e070f0b3dbef39 0.0s
=> => extracting sha256:e14d080185efc14b694993705b4c2aa793338873e156e253 2.2s
=> => extracting sha256:caae273acc14b6604dd1272fdc3ec3a136e2a1f0a496d4ee 0.0s
=> [web 2/4] WORKDIR /app 0.9s
=> [web 3/4] RUN apk update && apk upgrade && apk --update add 15.3s
=> [web 4/4] RUN gem update --system && gem install bundler --no-docum 10.1s
=> [web] exporting to image 2.8s
=> => exporting layers 2.8s
=> => writing image sha256:ede4cf3d48344c8fe15998ab3d6f20375d55c9ad69cf5 0.0s
=> => naming to docker.io/library/app-web 0.0s
Gemfile
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "3.1.2"
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", ">= 7.0.8"
docker-compose run --rm web bundle exec rails new . --database=postgresql
で新規Railsプロジェクトを作成します。
DBの情報を追加します。
config/database.yml
username: <%= ENV.fetch('POSTGRES_USER') { 'user' } %>
password: <%= ENV.fetch('POSTGRES_PASSWORD') { 'password' } %>
host: <%= ENV.fetch('DB_HOST') { 'db' } %>
port: <%= ENV.fetch('DB_PORT') { 5432 } %>
docker-compose run --rm web bundle exec rails db:create
でDBを作成します。
DB
➜ app git:(main) docker-compose run --rm web bundle exec rails db:create
[+] Building 0.0s (0/0)
[+] Creating 1/0
✔ Container app-db-1 Running 0.0s
[+] Building 0.0s (0/0)
Created database 'app_development'
Created database 'app_test'
docker-compose up
でサーバーを立ち上げます。
graphqlをインストールする
Gemfileにこちらのgemを追加し、インストールします。
group :development do
gem "graphiql-rails"
end
gem "faker"
gem "graphql"
graphql-rails
は、RailsアプリでGraphQLを実装するためのGemの1つです。
このGemは、Railsアプリ内でGraphQL APIを簡単にセットアップし、管理することができます。
GraphQLスキーマの定義、クエリの処理、リゾルバの作成などのタスクをサポートします。また、Railsのルーティングやコントローラーと統合して、GraphQLエンドポイントを提供することも可能です。
graphiql
は、GraphQLのクエリやミューテーションをテストおよびデバッグするためのWebベースのIDE(統合開発環境)です。
graphiql
を使用すると、ブラウザ上でGraphQLスキーマを検索し、クエリを作成し、実行して結果を視覚化することができます。これにより、GraphQLエンドポイントを直接ブラウザでテストすることが可能になります。graphiql
は、開発中やデバッグ時に便利です。
graphql-rails
とgraphiql
は、組み合わせて使用することで、Railsアプリ内で簡単にGraphQL APIを構築し、開発やデバッグの効率を向上させることができます。
インストーラーを実行する
rails generate graphql:install
create app/graphql/types
create app/graphql/types/.keep
create app/graphql/app_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/app_schema.rb
rails generate graphql:install
コマンドは、RailsアプリにGraphQLの基本構造をセットアップするためのジェネレーターです。このコマンドを実行すると、以下の作業が行われます。
- GraphQLの設定ファイルが作成されます。
- GraphQLのルートクエリ(Query)とミューテーション(Mutation)のディレクトリが作成されます。
- GraphQLのスキーマファイルが作成され、ルートクエリとミューテーションのエントリーポイントが定義されます。
- GraphQLのコントローラーが作成され、GraphQLのエンドポイントがマウントされます。
モデルを作成する
ユーザー、投稿、コメントモデルを作成します。
rails g model user name email
rails g model post user:references title content
rails g model comment user:references body post:references
rails db:migrate
モデルの関連付けを設定します。
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
end
class Post < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
end
class Comment < ApplicationRecord
belongs_to :user
belongs_to :post
end
faker
を使ってユーザーと投稿を作成します。
10.times do
user = User.create(
email: Faker::Internet.email,
name: Faker::Name.name
)
Post.create(
user: user,
title: Faker::Movie.title,
content: Faker::Movie.quote
)
end
rails db:seed
を実行します。
typeを作成する
GraphQLにおけるType(タイプ)は、GraphQLスキーマで使用される基本的な概念の一つです。Typeは、GraphQL APIがクライアントに公開するデータの型や構造を定義します。
データ構造を定義するために使用されるので、Type
は通常、スキーマ内で多くの場所で再利用されます。
主なTypeには以下のようなものがあります。
-
Scalar Type(スカラータイプ): スカラータイプは、単一の値を表すデータ型です。例えば、文字列、数値、真偽値、日付などがあります。代表的なスカラータイプには、
String
、Int
、Float
、Boolean
、ID
などがあります。 -
Object Type(オブジェクトタイプ): オブジェクトタイプは、複数のフィールドを持つ構造化されたデータ型です。オブジェクトタイプは、フィールドとその型を定義します。オブジェクトのフィールドには、他のType(スカラー、オブジェクト、リストなど)も含まれます。
-
List Type(リストタイプ): リストタイプは、複数の要素を持つ配列やリストを表します。リスト内の各要素は、指定された型のデータを持ちます。
-
NonNull Type(非nullタイプ): 非nullタイプは、その値がnullでないことを示します。GraphQLスキーマ内でフィールドや引数に
!
を付けることで、非nullタイプを定義することができます。
作成したユーザーモデルのフィールドとその型を定義します。
rails generate graphql:object user
create app/graphql/types/user_type.rb
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
rails generate graphql:object user
コマンドは、Railsアプリケーション内に新しいGraphQLのオブジェクトタイプを作成するためのジェネレーターです。
UserType
という名前のGraphQLオブジェクトタイプを生成されます。その中にユーザーモデルのフィールドやリレーションシップが定義されます。
ポストとコメントも同じように作成していきます。
rails generate graphql:object post
create app/graphql/types/post_type.rb
rails generate graphql:object comment
create app/graphql/types/comment_type.rb
GraphQLではカスタムフィールドを定義することができます。カスタムフィールドは、既存のモデルやオブジェクトの属性やリレーションシップに依存せず、任意の処理やデータを提供するために使用されます。
たとえば、特定の条件に基づいてユーザーのリストをフィルタリングしたり、複数のモデルからデータを結合して返したりするカスタムフィールドを定義することができます。
カスタムフィールドを定義するには、GraphQLのオブジェクトタイプにメソッドを追加する方法があります。これにより、リゾルバ内でカスタムロジックを処理できます。
GraphQLのUserオブジェクトタイプにカスタムフィールドを追加してみます。
# app/graphql/types/user_type.rb
module Types
class UserType < BaseObject
# カスタムフィールド
field :post_count, Integer, null: false
field :posts, [Types::PostType], null: false
# ここにカスタムフィールドのロジックを記述
# たとえば、特定の条件に基づいてデータを取得したり、計算したりする
def post_count
object.posts.size
end
end
end
UserType
にpost_count
というカスタムフィールドが追加されています。このフィールドは、String
型を返し、null
値を許容しません。
post_count
メソッド内で、ユーザーの投稿数のカスタムロジックを実装し、データを返すことができます。
posts
はモデルの関連付けにて定義されているため再定義する必要がないです。
QueryTypeを定義する
QueryType
は、GraphQLスキーマのエントリーポイントであり、クライアントがクエリを実行してサーバーからデータを取得するために使用するメソッドを定義します。
例えば、getUser
やgetAllUsers
などのフィールドを含むことができます。
QueryType
は通常、Type
のフィールドを含み、クライアントが取得できるデータを指定します。
module Types
class QueryType < Types::BaseObject
# 全てのユーザーを取得する
field :users, [Types::UserType], null: false
def users
User.all
end
# IDからユーザーを取得する
field :user, Types::UserType, null: false do
argument :id, ID, required: true
end
def user(id:)
User.find(id)
end
end
end
このように、QueryType
を定義することで、クライアントはGraphQLスキーマ内のエントリーポイントを使用してデータを取得できます。
検証
http://localhost:3000/graphiql
にアクセスします。
GraphQLがブラウザに表示されます。
ユーザーとユーザー毎の投稿数を取得してみます。
個別のユーザーを取得してみます。
終わり
graphQLのセットアップとデータの取得についてまとめてみました。
次回はデータを変更するための操作mutation
についてもまとめていきたいと思います。
Discussion