GraphQLのクエリを理解する
序章
このチュートリアルでは、GraphQLサーバーからデータを取得する方法をよりよく理解できるように、GraphQLのクエリについて詳しく見ていきます。 フィールド、引数、エイリアス、操作構文などについて説明します。 完了すると、クエリの機能を活用して、GraphQLでより優れたAPIを構築できるようになります。
田畑
GraphQLは、オブジェクトの特定のフィールドを要求することに基づいています。 したがって、フィールドについて話さずにクエリについてうまく話すことはできません。 クエリは、サーバーから特定のフィールドを要求するためにクライアントが使用する構造です。
GraphQLは、すべてのリクエストに対して1つのエンドポイントを最適に公開するように構成されているため、クエリは特定のフィールドをリクエストするように構成され、サーバーは、リクエストされている正確なフィールドに応答するように同様に構成されます。
クライアントがAPIエンドポイントからサッカー選手をリクエストしたい状況を考えてみましょう。 クエリは次のように構成されます。
{
players {
name
}
}
これは典型的なGraphQLクエリです。 クエリは、次の2つの異なる部分で構成されています。
root field
(プレーヤー):ペイロードを含むオブジェクト。payload
(名前):クライアントによって要求されたフィールド。
サーバーはクライアントが要求しているフィールドを認識しており、常にその正確なデータで応答するため、これはGraphQLの重要な部分です。 クエリ例の場合、次のような応答があります。
{
"players": [
{"name": "Pogba"},
{"name": "Lukaku"},
{"name": "Rashford"},
{"name": "Marshal"}
]
}
フィールドname
は、文字列型(この場合はマンチェスターユナイテッドの選手の名前)を返します。 ただし、文字列だけに限定されません。ルートフィールドplayers
がアイテムの配列を返すように、すべてのデータ型のフィールドを持つことができます。 GraphQLの公式ドキュメントでGraphQLTypeSystemの詳細をお気軽にご覧ください。
本番環境では、名前を返すだけでは不十分です。 たとえば、最後のクエリでは、クエリを再定義してリストから個々のプレーヤーを選択し、そのプレーヤーに関する追加のデータをクエリできます。 これを可能にするには、詳細を取得できるように、そのプレーヤーを識別する方法が必要になります。 GraphQLでは、引数を使用してこれを実現できます。
引数
GraphQLクエリを使用すると、引数をクエリフィールドとネストされたクエリオブジェクトに渡すことができます。 クエリ内のすべてのフィールドとネストされたオブジェクトに引数を渡して、リクエストをさらに深め、複数のフェッチを行うことができます。 引数は、RESTAPIの従来のquery parameters
またはURL segments
と同じ目的を果たします。 それらをクエリフィールドに渡して、サーバーがリクエストにどのように応答するかをさらに指定できます。
shirt size
やshoe size
などの特定のプレーヤーのキットの詳細を取得する以前の状況に戻ります。 まず、引数id
を渡してそのプレーヤーを指定し、プレーヤーのリストからプレーヤーを識別してから、クエリペイロードで必要なフィールドを定義する必要があります。
{
player(id : "Pogba") {
name,
kit {
shirtSize,
bootSize
}
}
}
ここでは、クエリに渡したid
引数のために、プレーヤーPogba
の目的のフィールドを要求しています。 フィールドと同様に、タイプの制限はありません。 引数もさまざまなタイプにすることができます。 id
引数を使用した前のクエリの結果は、次のようになります。
{
"player": {
"name": "Pogba",
"kit": [
{
"shirtSize": "large",
"shoeSize": "medium"
}
]
}
}
ここで考えられる落とし穴の1つは、GraphQLクエリが単一のアイテムとアイテムのリストでほぼ同じように見えることです。 このような状況では、スキーマで定義されている内容に基づいて、何を期待できるかを常に把握していることを忘れないでください。
また、GraphQLクエリはインタラクティブであるため、ルートフィールドオブジェクトにフィールドを自由に追加できます。 そうすることで、クライアントとして、ラウンドトリップを回避し、1回のリクエストで必要なだけのデータをリクエストできる柔軟性が得られます。
では、1人だけでなく、2人のプレーヤーに対して同じフィールドをフェッチしたい場合はどうなりますか? そこで、エイリアスが登場します。
エイリアス
最後の例を詳しく見ると、結果オブジェクトのフィールドが次のようになっていることがわかります。
// result....
"player": {
"name": "Pogba",
"kit": [
{
"shirtSize": "large",
"shoeSize": "medium"
}
]
}
クエリフィールドに一致します。
// query.... has matching fields with the result
player(id : "Pogba") {
name,
kit {
shirtSize,
bootSize
}
}
しかし、議論なしで:
(id : "Pogba")
このため、異なる引数を使用して同じフィールドplayer
を直接クエリすることはできません。 つまり、次のようなことはできません。
{
player(id : "Pogba") {
name,
kit {
shirtSize,
bootSize
}
}
player(id : "Lukaku") {
name,
kit {
shirtSize,
bootSize
}
}
}
これはできませんが、エイリアスを使用することはできます。 フィールドの結果の名前を任意の名前に変更できます。 この例では、2人のプレーヤーのキットの詳細をクエリするために、次のようにクエリを定義します。
{
player1: player(id: "Pogba") {
name,
kit {
shirtSize,
shoeSize
}
}
player2: player(id: "Lukaku") {
name,
kit {
shirtSize,
shoeSize
}
}
}
ここでは、2つのplayer
フィールドが競合していますが、異なる名前player1
とplayer2
にエイリアスできるため、1つのリクエストで両方の結果を取得できます。
{
"data": {
"player1": {
"name": "Pogba",
"kit": [
{
"shirtSize": "large",
"shoeSize": "medium"
}
]
},
"player2": {
"name": "Lukaku",
"kit": [
{
"shirtSize": "extralarge",
"shoeSize": "large"
}
]
}
}
}
エイリアスを使用して、同じフィールドを異なる引数で正常にクエリし、期待される応答を取得しました。
操作構文
これまで、操作名またはタイプのいずれかを明示的に定義する必要がない、省略形の操作構文を使用してきました。 本番環境では、操作名とタイプを使用して、コードベースのあいまいさを軽減することをお勧めします。 また、エラーが発生した場合のクエリのデバッグにも役立ちます。
操作構文は、次の2つの中心的な要素で構成されています。
-
operation type
は、クエリ、ミューテーション、またはサブスクリプションのいずれかです。 これは、実行する予定の操作のタイプを説明するために使用されます。 -
operation name
は、実行しようとしている操作に関連するのに役立つものであれば何でもかまいません。
これで、前の例を書き直して、次のような操作タイプと名前を追加できます。
query PlayerDetails{
player(id : "Pogba") {
name,
kit {
shirtSize,
bootSize
}
}
}
この例では、query
が操作タイプで、PlayerDetails
が操作名です。
変数
これまで、すべての引数をクエリ文字列に直接渡してきました。 ほとんどの場合、渡す引数は動的です。 たとえば、クライアントが詳細を求めているプレーヤーが、テキスト入力フォームまたはドロップダウンメニューから来ているとします。 クエリ文字列に渡す引数は動的である必要があり、そのためには変数を使用する必要があります。 したがって、変数は、クエリから動的な値を除外し、それらを個別のディクショナリとして渡すために使用されます。
最後の例を考えると、選択したプレーヤーの詳細が返されるようにプレーヤーを動的にしたい場合は、プレーヤーのID値を変数に格納し、次のように操作名とクエリ引数に渡す必要があります。
query PlayerDetails ($id: String){
player (id : $id) {
name,
kit {
shirtSize,
bootSize
}
}
}
ここで、$title: String
は変数定義であり、title
は変数名です。 プレフィックスは$
で、その後にタイプ(この場合は文字列)が続きます。 これは、動的クエリを作成するために文字列を手動で補間することを回避できることを意味します。
フラグメント
クエリを見ると、player
フィールドが両方のプレーヤーで実質的に同じであることがわかります。
name,
kit {
shirtSize,
bootSize
}
クエリをより効率的にするために、次のように、この共有ロジックをplayer
フィールドの再利用可能なフラグメントに抽出できます。
{
player1: player(id: "Pogba") {
...playerKit
}
player2: player(id: "Lukaku") {
...playerKit
}
}
fragment playerKit on player {
name,
kit {
shirtSize,
shoeSize
}
}
共有コードの一部を抽出し、それを複数のフィールドで再利用する機能は、開発者が開発や本番環境でさえ繰り返さないようにするための重要な概念です。 深く階層化されたコードベースで作業している場合は、複数のコンポーネント間で繰り返すのではなく、コードを再利用する方が便利でタイムリーであることがわかります。
指令
GraphQLディレクティブは、クエリに応答するときに特定のフィールドをinclude
またはskip
するかどうかをサーバーに指示する方法を提供します。 GraphQLには、それを実現するのに役立つ2つの組み込みディレクティブがあります。
@skip
:渡された値がtrueの場合に特定のフィールドをスキップします。@include
:渡された値がtrueの場合に、特定のフィールドを含めます。
Boolean
ディレクティブを追加し、@skip
ディレクティブを使用してサーバー上でスキップしてみましょう。
query PlayerDetails ($playerShirtDirective: Boolean!){
player(id: "Pogba") {
name,
kit {
shirtSize @skip(if: $playerShirtDirective)
bootSize
}
}
}
次に行うことは、クエリ変数にplayerShirtDirective
ディレクティブを作成し、それをtrueに設定することです。
// Query Variables
{
"itemIdDirective": true
}
これで、shirtSize
なしでペイロードが返されます。
"player": {
"name": "Pogba",
"kit": [
{
"shoeSize": "medium"
}
]
}
@include
ディレクティブを使用して、この状況を逆転させることができます。 @skip
ディレクティブの反対として機能します。 クエリで@skip
ディレクティブを@include
に置き換えることにより、サーバーでのこのスキップアクションを元に戻すことができます。
結論
この記事では、GraphQLクエリの詳細について説明しました。
GraphQLの詳細については、GraphQLの公式ドキュメントをご覧ください。 GraphQLを使用してプロジェクトを作成する場合は、 Ubuntu18.04チュートリアルでNode.jsとMongoDBを使用してGraphQLサーバーを構築およびデプロイする方法を試してください。