これは何
- Ruby のコードから GraphQL API を叩く方法を紹介します
- Ruby のコードで GraphQL API を作る方法は紹介しません
GraphQL とは
GraphQL とは何か、を一言で表現するのは難しいのですが、個人的には「API を表現するプロトコルの一種」というような認識です。
REST API と比較されることが多いです。REST API で
curl http://example.com/users/123/posts/
のようにして叩くところを、GraphQL では
curl http://example.com/graphql -X POST --data '
{
user("123") {
posts {
title
}
}
}
'
のように叩きます。REST API では /users/
/users/123/
/users/123/posts/
のように様々な URL にリクエストを投げて目的の情報を手に入れますが、GraphQL では 1 つのエンドポイントに様々なクエリを投げ分けることで、必要な情報を全て(不必要な情報は除いて)1 度のリクエストで取得することができます。
GraphQL API の利用方法を学ぶ
取り急ぎ GraphQL API を利用することができればよかったので、私は GraphQL 公式ガイド (https://graphql.org/learn/) の Queries and Mutations
と Schemas and Types
だけを読みました。やや長めのページ 2 つを読むだけで概要が理解できたのでおすすめです。
ただし注意点が 1 つあります。 例として登場する API の意味を深く考えてはいけません。 例えば、冒頭でこんなクエリが登場します。
{
hero {
name
}
}
これに対して、サンプル API はこんなレスポンスを返しています。
{
"data": {
"hero": {
"name": "R2-D2"
}
}
}
この例を見たとき、「hero_id を指定したわけでもないのになぜ R2-D2 を返すんだろう?」「hero_id を指定しないときは hero 全てを返す方がいいのでは?」というのが気になって、全体の理解の妨げになってしまいました。実際にはこの API はただのサンプルでしかないので、実用性などは無視して「そういうもの」と思い込む必要があります。単に「任意引数として Episode を 1 つ受け取り、Character を 1 つ返す、hero という Query」があるだけです。
学習用 GraphQL サーバーを立てる
上述した GraphQL 公式ガイドに出てくる謎のスターウォーズ API を再現したものが apollographql/starwars-server です。このあと Ruby から叩くのに使うので、さくっと起動しておきましょう。ブラウザから http://localhost:8080/graphql にアクセスできれば起動成功です。
なお、似た名前のものに graphql/swapi-graphql がありますが、これはより本格的なスターウォーズ API の GraphQL ラッパーなので別物です。気をつけましょう。
Ruby で GraphQL クライアントを使ってみる
Ruby 製の GraphQL Client として github/graphql-client があります。github がオーナーという安心感が凄かったので、ひとまずこれを使おうと思います。
README にも書かれていますが、以下のようにして使います。
require "graphql/client"
require "graphql/client/http"
module SWAPI
# http アダプターを設定
HTTP = GraphQL::Client::HTTP.new("http://localhost:8080/graphql")
# 上記を使って API サーバーから GraphQL Schema 情報を取得
Schema = GraphQL::Client.load_schema(HTTP)
# 上記を使ってクライアントを作成
Client = GraphQL::Client.new(schema: Schema, execute: HTTP)
end
HeroQuery = SWAPI::Client.parse <<~'GRAPHQL'
{
hero {
name
}
}
GRAPHQL
result = SWAPI::Client.query(HeroQuery)
pp result.to_h
上記では Schema 情報をサーバーから取得していますが、以下のようにしてあらかじめローカルに保存しておき、それをロードする方法もあるそうです。
module SWAPI
# http アダプターを設定(上例と同じ)
HTTP = GraphQL::Client::HTTP.new("http://localhost:8080/graphql")
# ローカルファイルから GraphQL Schema 情報を取得
Schema = GraphQL::Client.load_schema("path/to/schema.json")
# 上記を使ってクライアントを作成(上例と同じ)
Client = GraphQL::Client.new(schema: Schema, execute: HTTP)
end
# API サーバーから GraphQL Schema 情報を取得してローカルファイルに出力
GraphQL::Client.dump_schema(SWAPI::HTTP, "path/to/schema.json")
# ...
注意点
クエリに Operation Name を付ける場合、 SWAPI::Client.query()
の引数にモジュール名のように付けて渡す必要があります。
HeroQuery = SWAPI::Client.parse <<~'GRAPHQL'
# NameOfHero という名前をつけた場合・・・
query NameOfHero {
hero {
name
}
}
GRAPHQL
# HeroQuery ではなく HeroQuery::NameOfHero を渡す必要がある!
result = SWAPI::Client.query(HeroQuery::NameOfHero)
これを忘れると、 expected definition to be a GraphQL::Client::OperationDefinition but was GraphQL::Language::Nodes::Document (TypeError)
というエラーが起きます。
またこのため、必然的に小文字から始まる名前も付けられません(client.rb:241:in 'const_set': wrong constant name ... (NameError)
というエラーが起きます)。
この ↓ あたりの issue は全て単にこの仕様を見落としている気がします。
- https://github.com/github/graphql-client/issues/212
- https://github.com/github/graphql-client/issues/171
- https://github.com/github/graphql-client/issues/148
README に明記されていないので私もハマってしまいました・・・(コントリビュートチャンス?)