ここでは、Rails での GraphQL の基本的な使い方を整理していきます。
内容は、Udemy『Basics of GraphQL with Ruby on Rails』のセクション1「Introduction」とセクション2「Reading Data」を参考にしています。
詳細はそちらをご参照ください。
Introduction
GraphQL とは
GraphQLは、APIで使われるクエリ言語です。
2012年にFacebook社により作られ、2015年に公開されました。
1つのリクエストで横断的なデータを自由に取得することができるので、RESTful APIとの比較で、注目されているAPIの設計です。
GraphQL vs REST
GraphQL と REST の比較は次のとおりです。
GraphQL | REST | |
---|---|---|
利用方法 | HTTP | HTTP |
エンドポイント | 1つ | 複数 |
1リクエストで取得可能なリソース | 複数 | 1つ |
必要以上のデータ取得 | 起こらない | 起こりやすい |
GraphQLでは、APIのエンドポイントが1つで、必要なデータを必要な分だけ取得できます。
APIを利用する側としては、GraphQLの方がありがたいですね。
とはいえ、単純に「GraphQL もしくは RESTの方が良い!」というものではなく、それぞれに向き不向きがあるようです。
詳細はこちらを参照:REST vs. GraphQL: A Critical Review
この記事の著者(スゴ腕のAPI開発者)は、
「GraphQLは簡単で使いやすい。が、スケールには向かない、導入例が少ない、JSONでしかレスを返せない、古い設計への後戻り、etc.」
のように、 GraphQLの利用シーンは限定的 というスタンスです。
これを参考にすると、サクッと使いやすいAPIを作る時は、GraphQLで設計するのが向いています。
Railsでのセットアップ
RailsでGraphQLをセットアップする手順は次のとおりです。
まずはAPIモードでRailsプロジェクトを新規作成します。
$ rails new [project] --api --skip
Gemfileに graphql を追加します。
gem ‘graphql’
$ bundle install
そして、必要なファイルをインストールします。
$ rails g graphql:install
そうすると、app/graphql 配下に必要なファイルが生成されます。
テストツール GraphiQL
GraphQLには、「GraphiQL」というイケてるテストツールアプリがあります。
コマンドラインからもダウンロードできます。
Homebrewがあれば、必要なコマンドは次の1行です。
$ brew cask install graphiql
インストール後、Finderで「graphiql」を検索して、control 押しながらクリックすればOKです。
Reading Data
それでは、GraphQLを使ったHTTPによるデータの取得方法を具体的にみていきます。
「app/graphql/types」配下のファイルをいじっていきます。
リクエストとレスポンス
リクエストするエンドポイントは、前述のとおり1つで、「root_domain/graphql」のみです。
リクエストで送るGraphQLクエリは次のような感じです。
{
field(arg: "value") {
subField
}
}
レスポンスは、指定した field のみがJSONで返ってきます。
query_type.rb
「types/quey_type.rb」は、普通のRailsアプリでいう「routes.rb」のようなもので、HTTPリクエストで問い合わせられる「field」を定義していきます。
デフォルトでは次のように書かれています。
class Types::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
end
試しに、QrahpiQLを使って、test_field を取得してみます。
ちゃんと指定したデータがJSONで返ってきました。
{
"data": {
"testField": "Hello World!"
}
}
1つ注意すべき点は、Railsではスネークケースになってたフィールド名が、リクエスト・レスポンスでは、キャメルケースになっている点です。
ただし、これも filed の定義に「camelize: false」オプションを渡せば、全てスネークケースで利用できるようになります。
例えば次のような感じです。
class Types::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", camelize: false
def test_field
"Hello World!"
end
end
filed の書き方
基本構造
先ほどみた例を、もう一度みてみましょう。
# TODO: remove me
field :test_field, String, null: false,
description: "An example field added by the generator"
def test_field
"Hello World!"
end
field の第1引数は「フィールド名」、第2引数は「レスポンスのデータ型」、第3引数は「null: true/false」となっています。
第3引数「null: true/false」で、返り値が null でもよいかどうかを指定します。これは必須項目なので、全ての field で定義する必要があります。
レスポンスのデータ型
レスポンスのデータ型は次の5種類 です。
- Int:
- A signed 32‐bit integer.
- Float:
- A signed double-precision floating-point value.
- String:
- A UTF‐8 character sequence.
- Boolean:
- true or false.
- ID:
- The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable.
IDというのが少し特殊ですが、その名の通り、DBのIDに使われます。
クエリ引数
特定のIDのデータだけ取得したい場合などは、field に引数を指定する必要があります。
その場合は、field 定義に argument を追加します。
field :test_field, String, null: false,
description: "An example field added by the generator", camelize: false do
argument :name, String, required: true
end
def test_field(name:)
"Hello #{name}!"
end
ActiveRecord
Author というモデルを作り、そのデータをクエリで取得してみましょう。
まずモデルの生成です。
$ rails g model Author first_name:string last_name:string yob:integer is_alive:boolean
$ rails db:migrate
そして、適当にAuthorデータを作ります。
$ rails c
> Author.create first_name: "Keisuke", last_name: "Inaba", yob: 1988, is_alive: true
> Author.create first_name: "Stephen", last_name: "Hawking", yob: 1942, is_alive: false
.
.
.
それでは実際にデータを取得するクエリを書いていきます。
返り値をネストさせたい場合は、types配下に別ファイルを作って field を定義していきます。
今回であれば、Author の field なので「types/author_type.rb」に書いていきます。
コード例は次のとおりです。
# app/graphql/types/author_type.rb
class Types::AuthorType < Types::BaseObject
description "An author"
field :id, ID, null: false
field :first_name, String, null: true, camelize: false
field :last_name, String, null: true, camelize: false
field :yob, Int, null: false
field :is_alive, Boolean, null: true, camelize: false
end
新しく type.rb ファイルを作成するときは、「Types::BaseObject」を継承させます。
そして、この「AuthorType」を取ってくる field を「query_type.rb」に書きます。
特定の author と、全ての authors を取得する field を定義してみます。
# app/graphql/types/query_type.rb
class Types::QueryType < Types::BaseObject
field :author, Types::AuthorType,
null: true,
description: "One author" do
argument :id, ID, required: true
end
def author(id:)
Author.where(id: id).first
end
field :authors, [Types::AuthorType], null: false
def authors
Author.all
end
end
返り値が配列になる場合は、
[Types::AuthorType]
や
[Int]
のように記載します。
それでは、GraphiQLからリクエストしてみましょう。
ちゃんとレスポンスが取れました。
このように1つのエンドポイントで、データを取ってくることができるんですね。
カスタムフィールド
type.rb ファイルにカスタムフィールドを追加することもできます。
Author の first_name と last_name を合わせた「full_name」フィールドを作ってみましょう。
先ほどの author_type.rb を次のように修正します。
class Types::AuthorType < Types::BaseObject
description "An author"
field :id, ID, null: false
field :first_name, String, null: true, camelize: false
field :last_name, String, null: true, camelize: false
field :yob, Int, null: false
field :is_alive, Boolean, null: true, camelize: false
field :full_name, String, null: true, camelize: false
def full_name
([object.first_name, object.last_name].compact).join" "
end
渡ってくるオブジェクトは「object」でアクセスできます。
実際にGraphiQLでテストしてみましょう。
ちゃんといけてますね。