GraphQLはAPIに対するクエリ言語です。サーバーサイドのランタイムで、データに定義した型システムを用いてクエリが実行できます。GraphQLは特定のデータベースやストレージエンジンには縛られません。すでにあるコードやデータで利用できるバックエンドです。
本稿はGraphQL公式「Queries and Mutations」の解説にもとづいて、日本語で説明し直しました。邦訳ではありませんので、原文から記述を省いたり、逆にわかりにくい部分は補ったりもしています。
フィールド
GraphQLは、オブジェクトのフィールドを取得します。たとえば、hero
のname
を求めるのがつぎのクエリです。
{
hero {
name
}
}
すると、クエリからは同じ形状(shape)で結果が得られます。GraphQLではサーバーは、クライアントが何を求めているのか理解して、フィールドを返すのです。この例で、フィールドname
はString
型で、スターウォーズのhero
である"R2-D2"
が返されました。
{
"data": {
"hero": {
"name": "R2-D2"
}
}
}
なお、GraphQL公式サイトのコード例は、インタラクティブな環境です。コードを書き替えて結果が確かめられますので、ぜひ試してみてください(たとえば、フィールドname
のあとにappearsIn
を加えると登場回が得られます)。
前掲のコード例で問い合わせたhero
のフィールドname
から返されたのは文字列です。けれど、フィールドとしてオブジェクトも参照できます。その場合、オブジェクトの中のフィールドを選んでもかまいません。GraphQLのクエリでは、関連するオブジェクトとそのフィールドを横断して、ひとつのリクエストで関連するデータが取り出せます。問い合わせを繰り返すこれまでのRESTの設計と異なる点です。
{
hero {
name
# クエリにはコメントも加えられる
friends {
name
}
}
}
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
このコード例では、friends
フィールドが項目の配列を返していることにご注目ください。GraphQLのクエリでは、単独項目も複数のリストも同じに扱われるのです。どちらを求めるかは、スキーマの指定にもとづいて決められます。
引数
GraphQLでは、オブジェクトとフィールドを横断してデータが得られることに加え、フィールドには引数も渡せます。
{
human(id: "1000") {
name
height
}
}
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 1.72
}
}
}
RESTのようなシステムは、ひと組の引数しか渡せません。クエリパラメータとURLでリクエストするというかたちです。GraphQLでは、フィールドごとに、さらには入れ子のオブジェクトにも、それぞれの引数が与えられます。複数のAPIの読み込みが、ひとつにまとめられるのです。引数をスカラーフィールドに渡せば、データ変換の実装もクライアントごとに分けることなく、サーバー上で一度に済ませられます。
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 5.6430448
}
}
}
引数にはさまざまな型があります。 このコード例で用いたのは列挙型です。数の決まったオプション(この例では、長さの単位のMETER
またはFOOT
)のひとつを表します。 GraphQLには、デフォルトの型が備わっています。けれど、GraphQLサーバーは、転送形式にシリアル化できるかぎり、独自の型も宣言できるのです(GraphQLの型システムについては「Type system」参照)。
エイリアス
これまでのコード例では、クエリの結果はフィールド名で返されました。すると、同じフィールドに異なる引数を与えた複数の結果は、重複してしまって一度では取り出せそうにありません。そういうときに用いるのがエイリアスです。クエリのフィールドに別の名前を与えられますので、結果がその名前で得られるのです。
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
{
"data": {
"empireHero": {
"name": "Luke Skywalker"
},
"jediHero": {
"name": "R2-D2"
}
}
}
このコードでは、同じhero
フィールドに異なる引数を与えてリクエストしました。けれど、エイリアスで別名を与えたので、結果が一度で取り出せたのです。
フラグメント
アプリケーションの求めるデータが増えてくると、クエリも込み入ってきます。複数のクエリに用いる一部のフィールドの組み合わせが同じとき、使い回したい場合も生じるでしょう。そのようなフィールドの組み合わせを、切り出して定めるのがフラグメント(fragment
)です。
{
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}
{
"data": {
"leftComparison": {
"name": "Luke Skywalker",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
},
{
"name": "C-3PO"
},
{
"name": "R2-D2"
}
]
},
"rightComparison": {
"name": "R2-D2",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
この左右でデータを比べる例でも、fragment
を使わなければ、GraphQLのコードが冗長になったことはわかるでしょう。フラグメントは、アプリケーションの複雑なデータ要求を小分けして使い回すのに役立ちます。いくつものUIコンポーネントの初期データも、フラグメントに分けて組み合わせれば、まとめて取得することもできるのです。
フラグメントの中で変数を用いる
フラグメントは、クエリや変更のGraphQLドキュメントに宣言された変数を参照することもできます(後述「変数」参照)。
「Variables」
query HeroComparison($first: Int = 3) {
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
friendsConnection(first: $first) {
totalCount
edges {
node {
name
}
}
}
}
{
"data": {
"leftComparison": {
"name": "Luke Skywalker",
"friendsConnection": {
"totalCount": 4,
"edges": [
{
"node": {
"name": "Han Solo"
}
},
{
"node": {
"name": "Leia Organa"
}
},
{
"node": {
"name": "C-3PO"
}
}
]
}
},
"rightComparison": {
"name": "R2-D2",
"friendsConnection": {
"totalCount": 3,
"edges": [
{
"node": {
"name": "Luke Skywalker"
}
},
{
"node": {
"name": "Han Solo"
}
},
{
"node": {
"name": "Leia Organa"
}
}
]
}
}
}
}
このコード例では、クエリHeroComparison
に変数$first
のデフォルト値として3
が与えられているので、フラグメントcomparisonFields
のfriendsConnection
に渡されてedges
の配列がそれぞれ3件ずつ表示されるのです。
オペレーション名
前掲の変数を用いた構文は、クエリに名前(HeroComparison
)をつけていました。キーワードquery
に続けて加えた識別子がオペレーション名です。簡略構文では省けます。けれど、公開するアプリケーションでは、コードをはっきり示すために用いることがおすすめです。たとえば、前述「フィールド」の項に掲げたGraphQLコードにオペレーション名(HeroNameAndFriends
)を加えるとつぎのようになります(結果は同じなので省略)。
query HeroNameAndFriends {
hero {
name
friends {
name
}
}
}
オペレーションタイプは操作の目的を示し、用いることができるキーワードはつぎの3つです。前掲コード例のようにオペレーションに変数を与えたいときは、オペレーションタイプとオペレーション名は省けません。
- query
- mutation
- subscription
オペレーション名は、操作に明示的に意味を与えます。GraphQLのマルチオペレーション文書には必須です。けれど、そうでなくてもデバッグやサーバーサイドのログにとても役立ちます。ネットワークやGraphQLサーバーにエラーなどの問題があるとき、コードベースでクエリや更新に名前を与えてあれば、中身をいちいち精査することなくリクエストは特定しうるからです。
変数
これまで、引数は基本的にクエリ文字列の中に記述してきました。ただ、多くのアプリケーションでは、フィールドに渡す引数は動的でしょう。たとえば、ドロップダウンからスターウォーズのエピソードを選んだり、フィールドの検索やフィルタを定める場合です。このようなとき、引数として変数を渡します。手順はつぎの3つです。
- クエリの中の動的に与えたい(静的な)値を変数(
$variableName
)に置き替える。 - クエリに変数(
$variableName
)を宣言して受け取る。 - 転送のための変数辞書(通常はJSON)で別途変数値(
variableName: value
)を渡す。
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}
"episode": "JEDI"
}
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
変数を用いたことにより、変更された値にもとづいてクエリ文字列をクライアントのコードでつくり直すような作業は要らなくなります。さらに、どの引数値が動的に変わるのかも明らかになるのです。
変数を定義する
変数は接頭辞$
をつけて定めます。そして変数名、コロン(:
)のあとに続くのがデータ型です。
$episode: Episode
変数として宣言できるのは、つぎの3つの型です
- スカラー
- 列挙型
- 入力オブジェクト型
フィールドに複雑なオブジェクトを渡したいときは、サーバーで一致する入力オブジェクト型を知らなければなりません。
変数の定義は省ける場合と必須な場合があります。前掲の型の定めEpisode
のあとに!
記号がないのは、省略できるということです。変数を渡すフィールドにnull
以外の引数が求められる場合は変数が省けません。
デフォルト変数値
クエリの変数にデフォルト値を与えるには、型定義のあとに=
演算子で加えてください。
query HeroNameAndFriends($episode: Episode = JEDI) {
hero(episode: $episode) {
name
friends {
name
}
}
}
すべての変数にデフォルト値が定められていれば、値を渡すことなくクエリは呼び出せます。変数辞書の一部に値が渡されているときは、デフォルト値は上書きされるのです。
ディレクティブ
変数により、クエリ文字列を手書きで変換することなく、動的なクエリができました。引数に変数を渡すのは、とても有効な手法です。さらに、変数を用いて、クエリの構成や形状も動的に変えられます。たとえば、フィールドの数の異なる要約や詳細表示のUIコンポーネントが求められる場合です。
query Hero($episode: Episode, $withFriends: Boolean!) {
hero(episode: $episode) {
name
friends @include(if: $withFriends) {
name
}
}
}
{
"episode": "JEDI",
"withFriends": false
}
{
"data": {
"hero": {
"name": "R2-D2"
}
}
}
変数withFriends
にfalse
を渡したので、結果にフィールドfriends
は表示されません。値をtrue
に変えると、friends
フィールドが結果に含まれます。
{
"episode": "JEDI",
"withFriends": true
}
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
GraphQLのfriends
フィールドに定めた@include(if: $withFriends)
がディレクティブです。ディレクティブは、フィールドまたはフラグメントに加えられ、サーバーに対してクエリの実行結果が変えられます。コアGraphQL仕様に備わっているのは、つぎのふたつのディレクティブです。GraphQLの仕様に準拠したサーバーの実装でサポートされます。
-
@include(if: Boolean)
- 引数がtrue
のときフィールドが結果に含まれる。 -
@skip(if: Boolean)
- 引数がtrue
のときフィールドが結果から除かれる。
ディレクティブにより、クエリ文字列を書き替えることなく、フィールドの追加や削除ができるのです。サーバーの実装にディレクティブを定めて、新たな機能も加えられます。
変更(Mutations)
GraphQLで採り上げるお話は、多くの場合データの取得に関わります。けれど、データプラットフォームとしては、サーバー側のデータを書き替えたいこともあるでしょう。
RESTでは、リクエストによってサーバーに何らかの副作用を起こすかもしれません。そのとき、GET
リクエストでデータは書き替えないよう勧められるのが通常です。同じように、GraphQLでもデータを変更する操作は明示的にmutation
で送るのがよいでしょう。
クエリの場合と同じく、変更するフィールドから返るのがオブジェクト型のときは、入れ子にしたフィールドを要求できます。更新後にオブジェクトの新しい状態を取り出す場合に役立つでしょう。
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}
createReview
フィールドが、新たにつくられたreview
のstars
とcommentary
を返していることにご注目ください。これはすでにあるデータを変更するときに役立ちます。たとえば、フィールドを加算したい場合に、新たな値への変更と取得をひとつのリクエストでできるからです。
このコード例で、渡した変数review
がスカラーでないことに気づいたかもしれません。この変数は、入力オブジェクト型です。特別なオブジェクト型で、引数として渡せます。
複数フィールドの変更
クエリと同じように、複数のフィールドを含めた変更も可能です。クエリ(query
)と変更(mutation
)の間には、 名前のほかにひとつ重要な違いがあります。クエリのフィールドが並列に実行されるのに対し、変更はフィールドを直列にひとつつずつ処理するのです。
これにより、ひとつのリクエストでフィールドincrementCredits
の変更をふたつ送った場合、はじめの書き替えはふたつめが始まる前に必ず終わっていることを保証します。順序の入れ違いによって意図しない結果になることが避けられるのです。
インラインフラグメント
他の多くの型システムと同じく、GraphQLスキーマにはインタフェースとユニオン型(union types)を定める機能が備わっています。インタフェースやユニオン型が返されるフィールドにクエリを実行する場合、インラインフラグメントの使用により、含まれる具体的な型にもとづいてデータを参照しなければなりません。つぎのコードがその例です。
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}
{
"ep": "JEDI"
}
{
"data": {
"hero": {
"name": "R2-D2",
"primaryFunction": "Astromech"
}
}
}
このクエリでhero
が返すのは、Character
型です。引数episode
によって、Droid
かHuman
のいずれかになります。直接選ぶときは、name
のようなCharacter
インタフェースに備わるフィールドしか問い合わせできません。
具体的な型のフィールドが求められるときは、型条件の示されたインラインフラグメントを用いなければならないのです。ひとつめのフラグメントは゛... on Droid゛ラベルづけされているので、hero
から返されたCharacter
がDroid
型のときのみprimaryFunction
フィールドが実行されます。
{
"ep": "EMPIRE"
}
{
"data": {
"hero": {
"name": "Luke Skywalker",
"height": 1.72
}
}
}
同じように、Character
がHuman
型のときは、ふたつめのフラグメントによりフィールドはheight
になるのです。
名前つきフラグメントも扱い方は変わりません。名前つきフラグメントには、必ず型が加えられるからです。
メタフィールド
GraphQLサービスからどのような型が返ってくるか、わからない状況が考えられます。すると、クライアント側でそのデータをどう扱ったらよいか知りたいでしょう。GraphQLでは、メタフィールド__typename
が要求できます。このフィールドにより、クエリのどこからでもその時点のオブジェクトの名前が得られるのです。
{
search(text: "an") {
__typename
... on Human {
name
}
... on Droid {
name
}
... on Starship {
name
}
}
}
{
"data": {
"search": [
{
"__typename": "Human",
"name": "Han Solo"
},
{
"__typename": "Human",
"name": "Leia Organa"
},
{
"__typename": "Starship",
"name": "TIE Advanced x1"
}
]
}
}
このコード例のクエリのsearch
からは、3つの型条件のインラインフラグメントのうちのひとつを示すユニオン型が返されます。クライアントは、__typename
フィールドにより型の違いを知ることができるのです。
GraphQLサービスには、ほかにもいくつかのメタフィールドがあります。それらは、Introspectionシステムを公開するために用いられます。
シリーズGraphQLの基本
「GraphQL: クエリ(queries)と変更(mutations)」
「GraphQL: スキーマと型」
「GraphQL: 検証(validation)」
「GraphQL: 実行」
「GraphQL: イントロスペクション(introspection)」