🔖

今更GraphQL:Query書き方基礎の基礎

2023/07/11に公開

ポートフォリオ作成のためGatsbyを試してみる中でGraphQLを触ることになったのですが、わからん過ぎたのでメモ。とりあえずデータの取得がしたいので、Queryだけ触れようと思います

GraphQLとは?

全体的な概要についてはこちらの記事がとても参考になりました。

GraphQLはWeb APIを開発するためのクエリ言語である。REST APIの問題を解決するために、Facebook(Meta)によって開発された。Web APIの開発において、REST APIと比較して柔軟かつ効率的なアプローチを提供できる。さらに、GraphQLではクライアントが必要なデータの構造を定義することができ、サーバから定義したものと同じ構造のデータが返される。

上記読んで個人的に勝手な感想ですが、今までフロントはREST APIでバックエンドに問い合わせて、バックエンドがSQLなどのクエリ言語を使ってデータを取得して返却していました。一方、GraphQLはフロントから直接SQL的なものを叩いて、好きなデータを好きな形でとってこれるので、フロントにDBが接続されたかのようなイメージを持ちました。

Queryとは?

GraphQLで開発されたAPIは、データの問い合わせ(query)、書き換え(mutation)や購読(subscription)をサポートしている。

上記の通り、GraphQLでは下記が実行できるとのこと。今回はこの中のQueryに焦点を当てて見ていきます。データの取得、つまりRESTにおけるGETにあたる部分…と思ったのですが、QueryはPOSTされてそうですしHTTPメソッドに絡めるとちょっとややこしいですね…こちらの通りSQLに例えた方がわかりやすそうです。

GraphQL できること SQLで例えるなら
Query データの問い合わせ SELECT
Mutation データの書き換え INSERT/UPDATE/DELETE
Subscription 購読(GraphQLサーバにデータ変化などの特定のイベントが生じるたびにクライアント側に通知)

具体的には公式を見つつ、こちらのSTAR WARSのエンドポイントを使って試してみます。

画面の見方

画面は大きく3つの領域に分かれているようで、左にDocumentなどの操作領域、中央がクエリを記述するところ、右にそのレスポンスという構成のようです。

一番左の領域にあるのがデータ構造の様子。ここから取得したいデータを選んでいくようです。よく見るとallXxxs(allFilmsなど)とxxx(filmなど)の対になっています。
allXxxは例えば映画一覧画面などで複数の映画に対するデータを取得したい場合、xxxは映画の詳細画面で単体の映画のデータがほしい場合に使用するようです。

queryの書き方

シンプルなデータ取得

試しに映画のタイトル一覧を取得してみます。左の領域でポチポチ選ぶだけで自動でクエリを作ってくれます(手動で入れても問題ないです)

query ExampleQuery {
  allFilms {
    films {
      title
    }
  }
}
response
{
  "data": {
    "allFilms": {
      "films": [
        {
          "title": "A New Hope"
        },
        {
          "title": "The Empire Strikes Back"
        },
        {
          "title": "Return of the Jedi"
        },
        {
          "title": "The Phantom Menace"
        },
        {
          "title": "Attack of the Clones"
        },
        {
          "title": "Revenge of the Sith"
        }
      ]
    }
  }
}

構造通りにほしいデータを指定していくだけで、同じ形のレスポンスが返ってきます。すごい。

operation type

query部分はoperation typeといい、取りうる値はquery/mutation/subscription。queryの時は省略可とのこと。

operation name

ExampleQuery部分はoperation nameといい、こちらも省略可。operation typeなしのoperation nameだけもレスポンスは返ってきましたが、無視されているだけで文法としては不正な様子。query Name {}query {}または{}が文法的には正しいようです。

結果としてquery ExampleQueryの部分は文法的にはなくても良さそうですが、公式的には明示した方がデバッグとかのときにもいいし、わかりやすいから書いといた方がいいんじゃないか、とのことでした。

operaton type/nameは省略可だが、公式は書くことを推奨
{
  allFilms {
    films {
      title
    }
  }
}

あと、オブジェクトの場合中身のプロパティはいちいち指定する必要がありそうなので、プロパティが多いときは全部書かなきゃいけないのでめんどうだなとは思いました…(オブジェクトだけ選んだら全部とってきてくれたらいいのに…左の領域で全部一気に選べるボタンはありました。)でもその分無駄なプロパティはとってこなくなりそう。

複数データ取得

RESTであれば別の種類のデータがほしい場合、基本的には別のAPIを叩いていましたが、GraphQLであれば1回のリクエストで取得できます。例えば下記で全映画データと全登場人物データに同時にアクセスできます。

query ExampleQuery {
  allFilms {
    films {
      title
    }
  }
  allPeople {
    people {
      name
    }
  }
}

Arguments

とってくるデータに条件をつける事ができます。例えば登場人物をIDで絞る、など。

{
  person(id: "cGVvcGxlOjE=") {
    id
    name
  }
}

ただ、このサイトをみる限り何でも指定できるわけではなく、予め設定されたものしか無理なようです。例えばpersonだとidとpersonIdのみが指定可能で、その他のプロパティを指定してもエラーになります。

また、heightの単位を変えるなどの処理(下記だとFOOTを指定している)もできるようですが、これも事前の定義がいると思われます。(STAR WARSでは変更可能な値は見つけられませんでした。。)

{
  human(id: "1000") {
    name
    height(unit: FOOT)
  }
}
response
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 5.6430448
    }
  }
}

GraphQL側で事前の設定は必要なようですが、かなり自由度の高いプロパティの受け渡しができるようでした。

Variables

変数を渡すこともできるようです。ただしこの場合、先頭のqueryは必須のようです。名前(ExampleQuery)はなしでもいけました。(ちなみにIDはStarWars側で用意されている型です)

query ExampleQuery($personId: ID) {
  person(id: $personId) {
    id
    name
  }
}

variables自体は中央の領域下部にてjson形式で指定できます。

variables
{
  "personId": "cGVvcGxlOjE="
}

複数はカンマ区切りで、=を使ってデフォルトの値も入れられるようです。また、型の後ろに!をつけると必須化。

query ExampleQuery($personId1: ID! = "cGVvcGxlOjI=", $personId2: ID) {
  person(id: $personId1) {
    id
    name
  }
  person(id: $personId2) {
    id
    name
  }
}

Aliases

プロパティ名のリネームができるようです。下記のようにプロパティ名部分を変更したい名前: プロパティ名とすれば、レスポンスに反映されます。

query  {
  person(id: "cGVvcGxlOjE=") {
    renamedName: name
  }
}
response
{
  "data": {
    "person": {
      "renamedName": "Luke Skywalker"
    }
  }
}

Fragments

下記のように同じ構造の複数データを取って来たい場合などに、共通化できるのがFragmentsのようです。(ちなみにLuke: personなどとしているのはプロパティ名がpersonで被ってしまいエラーになるためです)

query  {
  Luke: person(id: "cGVvcGxlOjE=") {
    id
    name
    species {
      language
      name
      skinColors
    }
    homeworld {
      gravity
      name
    }
  }
  C3PO: person(id: "cGVvcGxlOjI=") {
    id
    name
    species {
      language
      name
      skinColors
    }
    homeworld {
      gravity
      name
    }
  }
}

共通化すると↓こんな感じ

query  {
  Luke: person(id: "cGVvcGxlOjE=") {
    ...personFields
  }
  C3PO: person(id: "cGVvcGxlOjI=") {
    ...personFields
  }
}

fragment personFields on Person {
    id
    name
    species {
      language
      name
      skinColors
    }
    homeworld {
      gravity
      name
    }
}

Directives

レスポンスに条件をつけることができるようです。例えば下記のようにすると、$ifの値によってspeciesをレスポンスに含めるかどうか決められます。

query ($id: ID!, $if: Boolean!)  {
  person(id: $id) {
    id
    name
    species @include(if: $if) {
      language
      name
      skinColors
    }
  }
}

以下2種類があるとのこと。(ifだけでいいんじゃ…?同じ値を使うときとかに否定ができないから…?)

  • @include(if: Boolean): trueなら含める
  • @skip(if: Boolean): trueなら含めない

まとめ

これである程度GraphQLのQueryリクエストの意味がわかるようになったはず…
実務で触ったことがないので想像でしか無いのですが、フロントとしては自由度が上がって嬉しい反面、責務も増えて管理は大変になりそう…?でもシンプルにちょっと楽しかったです。

Discussion