アニメオタク、GraphQLのクエリを学ぶ

# はじめに
こんにちは。株式会社HRBrainで内定者インターンをしている橋本です。主にバックエンドの開発をしています。
この記事ではGraphQLのクエリの書き方を、[公式ドキュメント](https://graphql.org/learn/queries/)の流れに沿って説明していきたいと思います。
本記事はHRBrain Advent Calendar 2022の21日目の記事です。

https://qiita.com/advent-calendar/2022/hrbrain

## なぜこれを取り上げようと思ったか
インターンとして働いているなかでGraphQLに触れる機会があったのですが、当時私はGraphQLを扱うのが初めてで、クエリの書き方がよくわからず困ってしまいました……。
理解してしまえばなんてことないのですが、最初はかなり苦労した覚えがあるので、そんな過去の私と同じような経験をする人を減らすべく、一歩ずつ丁寧にクエリの書き方について説明したいと思いこの記事を作成するに至りました。
またアニメ好きである私にドンピシャのGraphQLサーバーが提供されていると知ったことも理由の1つです。これは後ほど紹介します。
## この記事で説明すること、しないこと
### 説明すること
* クエリの基本的な書き方
* 引数について
* フラグメントについて
* ディレクティブについて
* インラインフラグメントについて
### 説明しないこと
* GraphQLとは
* Query,Mutationとは
* スキーマ定義の書き方
* GraphQLサーバーの実装方法


# 今回取り扱うGraphQLサーバー、AniList APIv2について
今回はこちらのAPIを用いて、クエリの書き方について説明していきたいと思います。[^1]

https://anilist.gitbook.io/anilist-apiv2-docs/

これは[AniList](https://anilist.co/home)という、気に入ったアニメなどを簡単に管理できるWebアプリが提供しているAPIで、膨大なデータベースからアニメや漫画の検索、キャラクターやスタッフの情報の取得などが行えます。[ガイドライン](https://anilist.gitbook.io/anilist-apiv2-docs/)に沿った非営利目的での利用は無料と案内されているので、ありがたく使わせてもらうことにします。
さらに嬉しいのは[こちら](https://anilist.co/graphiql)からブラウザ上で動かすこともできるということです。面倒な環境構築などは必要ありません。[^2]
使い方もシンプルでわかりやすいです。
![image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/59a19ad2-e573-7a63-5650-feff5c69b885.jpeg)

また、スキーマ定義などは以下のリンクから確認できます。

https://anilist.gitbook.io/anilist-apiv2-docs/

眺めてるだけでワクワクしてきますね。
それでは早速このAPIを用いてGraphQLのクエリの書き方について学んでいきましょう。
[^1]: 他にも簡単に試せるGraphQLサーバーはいろいろあり、[こちら](https://ivangoncharov.github.io/graphql-apis/)にリストアップされてまとめられてます。
[^2]: GraphiQLとはブラウザ上で動かせるGraphQL IDEの1つです。GraphQL Playgroundとほとんど同じように扱えます。
# 本編
## Fields, Arguments
GraphQLにおける基本的なクエリの書き方は以下の通りです。
```plaintext:基本的なクエリの書き方
{
  クエリ名 (引数) {
    フィールド1
    フィールド2
  	...
    フィールドN
}
```
この書き方にならって`Media`というQueryを叩いてみましょう。これは引数にとった値から該当するアニメや漫画の情報を取得するQueryです。最初なので丁寧に進めます。

クエリを叩くためにまずは`Media`のスキーマ定義を確認しましょう。
[こちら](https://anilist.github.io/ApiV2-GraphQL-Docs/)の検索窓に`Media`と入力してみると、以下のような検索結果が得られると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/729295f0-3b64-f2bc-a14f-501b0bbbce70.png)
ふむふむ、引数には`id`,`idMal`,`startDate`などを入れることができ、そこから`Media`という型で戻り値を得ることができるのですね、（説明も簡潔についていてわかりやすい！）
また`Media`型の詳細についても確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/4075fa59-cd5d-207d-db5f-7d2e14c6d219.png)

今回は`search`という引数に`one piece`を指定することにしましょう。また得られる`Media`型の戻り値のなかでも`genres`とそのアニメの人気度を表す`averageScore`というフィールドを指定してクエリを叩いてみることにします。最終的なクエリは以下のようになります。

```graphql:クエリ
{
  Media (search:"one piece") {
    genres
    averageScore
  }
}
```
ちなみにクエリの入力中に`Ctrl+C`を押すと補完が聞くので、適宜活用すると便利です。
それでは早速画面上部の▶ボタンを押してクエリを実行してみましょう！すると画面右側に次のようなレスポンスが表示されます。
```json:レスポンス
{
  "data": {
    "Media": {
      "genres": [
        "Action",
        "Adventure",
        "Comedy",
        "Fantasy"
      ],
      "averageScore": 91
    }
  }
}
```
バッチリデータを得ることができています。`averageScore`が`91`とは、さすがワンピースは人気ですね。これがGraphQLの基本的なクエリの書き方になります。

次は`title`フィールドを取得してみましょう。これはその名の通り、メディアのタイトル情報を表すフィールドです。
スキーマ定義を確認すると、`title`フィールドは`MediaTitle`型のオブジェクトを返すようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/61c7b395-b882-8eaf-cc1c-c77e6266d040.png)
また`MediaTitle`型には`romaji`,`english`,`native`,`userPreferred`というフィールドがあり、それぞれ「現地語でのローマ字表記」、「英語圏での公式タイトル」、「現地語での公式タイトル」、「認証済みユーザーの選択言語」が対応しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/eee78a7f-c4f1-9c40-92eb-40fea5b27c3c.png)
今回は`romaji`と`english`を指定してクエリを叩いてみます。この場合のクエリとレスポンスは以下のようになります。フィールドをネストしている部分が前回と違いますね。

```graphql:クエリ
{
  Media (search:"one piece") {
    title{
      native
      english
    }
  }
}
```
```json:レスポンス
{
  "data": {
    "Media": {
      "title": {
        "native": "ONE PIECE",
        "english": "One Piece"
    }
  }
}
```
なんと、英語圏の公式タイトルは日本のものと違うんですね。初めて知りました……。


## Aliases
さて、次はワンピースの人気度を他のアニメと比べてみることにしましょう。
先ほどの書式通りに書くと次のようになります。しかしこれだとエラーが発生し、期待したレスポンスを得ることができません。
```graphql
{
  Media (search:"one piece") {
    title{
      native
    }
    averageScore
  }
  Media (search:"banana fish") { # banana fishというアニメと比較する。
    title{
      native
    }
    averageScore
  }
}
```
```json:レスポンス
{
  "errors": [
    {
      "message": "Fields \"Media\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.",
      "status": 400,
      "locations": [
        {
          "line": 2,
          "column": 3
        },
        {
          "line": 8,
          "column": 3
        }
      ]
    }
  ],
  "data": null
}
```
このエラーの原因は、（エラーメッセージにもあるように）戻り値に`Media`というフィールドが複数含まれ、衝突が起こるためです。これを回避するために`alias`を利用します。
なにも難しいことはなく、クエリの前に`alias名:`を追加するだけです。
```diff_graph_ql:クエリ
{
- Media(search: "one piece") {
+ ONE_PIECE: Media (search:"one piece") {
    title {
      native
    }
    averageScore
  }
- Media(search: "banana fish") {
+ BANANA_FISH: Media(search: "banana fish") {
    title {
      native
    }
    averageScore
  }
}
```
```json:レスポンス
{
  "data": {
    "ONE_PIECE": {
      "title": {
        "native": "ONE PIECE"
      },
      "averageScore": 91
    },
    "BANANA_FISH": {
      "title": {
        "native": "BANANA FISH"
      },
      "averageScore": 84
    }
  }
}
```
私のお気に入りのアニメである[BANANA FISH](https://bananafish.tv/)は惜しくも84点でした。良いアニメなので皆さんぜひ見てみてください。（布教）[^3]
[^3]: 私は受験生のころにBANANA FISHを見たのですが、その怒涛の展開に心を支配されてしばらく勉強に身が入らなくなってしまいました。それくらい面白いアニメです。

## Fragments
お次はフラグメントです。フラグメントを使うと一連のフィールドを再利用できます。
実は先ほどのクエリには少し冗長な部分があるので、それをフラグメントを用いて書き直してみましょう。
冗長なのは具体的には以下の部分です。このフィールド指定がクエリの中で2回繰り返されています。
```graphql:冗長な部分
    title {
      native
    }
    averageScore
```
今回は指定しているフィールドが少なく、かつ繰り返しも2回だったのでそこまで問題にはなりません。しかし実際のプロダクトコードのなかでは膨大なフィールドの塊を何回も再利用する場面が出てきます。そうなると非常にコードが読みづらくなります。考えるだけで恐ろしいです。

それを解決するのがフラグメントです。百聞は一見にしかず、ということで書き直した例を早速見てもらいましょう。
```graphql
{
  ONE_PIECE: Media(search: "one piece") {
    ...TitleAndScore
  }
  BANANA_FISH: Media(search: "banana fish") {
    ...TitleAndScore
  }
}

fragment TitleAndScore on Media {
  title {
    native
  }
  averageScore
}
```
先ほど繰り返していた冗長な部分が`TitleAndScore`というフラグメントにまとめられてスッキリしました。`on Media`というのは「`TitleAndScore`というフラグメントが`Media`型の一部のフィールドを指定している」ということを表しています。

`Fragment`はもともと欠片という意味だと思い出せば理解しやすいですね。
## Operation name
今まで書いてきたクエリには任意で名前をつけることができます。これをオペレーション名といいます。オペレーション名をつけることで、そのクエリが何を取得するものなのかが一目でわかるようになります。

具体的な実装方法としては、今まで書いてきたクエリの先頭に`(クエリ形式) (オペレーション名)`とつけるだけです。クエリ形式には`query`、`Mutation`、`subscription`の3種類がありますが、今回のクエリ形式はデータを取得する`query`なので次のようになります。

```diff_graph_ql
+ query ComparisonToOnePiece{
- {
  ONE_PIECE: Media(search: "one piece") {
    ...TitleAndScore
  }
  BANANA_FISH: Media(search: "banana fish") {
    ...TitleAndScore
  }
}

fragment TitleAndScore on Media {
  title {
    native
  }
  averageScore
}
```
レスポンスは先ほどと全く変わりません。ただクエリに名前をつけただけなので。
## Variables
今までは引数をクエリのなかにベタ書きしていましたが、これを外から渡せるような形式にしてみましょう。
具体的には、外から引数として渡したアニメタイトルとワンピースの人気度を比較できるようにしてみましょう。
これは[公式のドキュメント](https://graphql.org/learn/queries/#variables)にもあるように、次の3ステップで実装できます。
1. オペレーション名に続けて外から受け取る引数を`$(変数名)`という書式で指定し、その型を書きます。 
 (今回は`Title`という引数を`String`型で受け取る。)
1. 受け取った引数を使いたいクエリには`$(変数名)`という形で渡します。（今回の場合だと`search: $Title`）
1. 引数をjson形式で渡します。

この手順に沿って書き換えたクエリと引数は以下のようになります。
```graphql:クエリ
query ComparisonToOnePiece($Title: String) {
  ONE_PIECE: Media(search: "one piece") {
    ...TitleAndScore
  }
  MyFavoriteAnime: Media(search: $Title) {
    ...TitleAndScore
  }
}

fragment TitleAndScore on Media {
  title {
    native
  }
  averageScore
}
```
```json:Query Variables
{
  "Title": "banana fish"
}
```
GraphiQLでは左下に変数を入力するところがあることに注意してください。ここまで入力して実行すると、期待通りの結果が得られます。これでいろんなアニメとワンピースを比較することが、渡す引数を変更するだけでできるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/1fe00dcb-b43f-0e3e-4b1f-59ea615fca35.png)


また型定義の横に`!`を入れるとその引数が必須であることを要請できます。
```graphql
query ComparisonToOnePiece($Title: String!) {
# 略
}
```
さらに型宣言の後にデフォルト値を追加することで、クエリ内の変数にデフォルト値を割り当てることもできます。このようにすると引数に何も渡さなかった場合はこのデフォルト値が代わりに渡されることになります。
```graphql
query ComparisonToOnePiece($Title: String = "banana fish") {
# 略
}
```
## Directives
ディレクティブはフィールドまたはフラグメントに添付することができ、クエリの実行に影響を与えることができます。早速例を見てみましょう。
```diff_graph_ql:クエリ
- query ComparisonToOnePiece($Title: String) {
+ query ComparisonToOnePiece($Title: String, $GetID: Boolean!) {
  ONE_PIECE: Media(search: "one piece") {
    ...TitleAndScore
  }
  MyFavoriteAnime: Media(search: $Title) {
    ...TitleAndScore
  }
}

fragment TitleAndScore on Media {
+  id @include(if: $GetID)
  title {
    native
  }
  averageScore
}
```
新しい`GetID`という引数と`@include`というオプションが追加されました。
この`@include`というのがディレクティブです。もう一つ`@skip`というディレクティブも用意されています。これらは次のような機能をもっています。
| ディレクティブ | 説明 |
|:-:|:-:|
| `@include(if: Boolean)`  | このデイレクティブをつけたフィールドは、引数が`true`の場合のみ結果に含める。  |
| `@skip(if: Boolean)`  | このデイレクティブをつけたフィールドは、引数が`true`の場合はスキップされる。   |

名前そのまんまの感じで覚えやすいですね。
つまり今回行った変更としては、ディレクティブ`@include`と新しい引数である`GetID`を追加して、`id`というフィールドを取得するかどうかを引数を経由することで選択できるようになったということです。
引数`GetID`をフラグメントの定義内から参照できているところもポイントですね。
それでは実際に引数に`"GetID":true`を追加してクエリを実行してみましょう。
```json:Query Variables
{
  "Title": "banana fish",
  "GetID": true
}
```
```json:レスポンス
{
  "data": {
    "ONE_PIECE": {
      "id": 30013,
      "title": {
        "native": "ONE PIECE"
      },
      "averageScore": 91
    },
    "MyFavoriteAnime": {
      "id": 100388,
      "title": {
        "native": "BANANA FISH"
      },
      "averageScore": 84
    }
  }
}
```
確かに`id`を取得できていますね。

このディレクティブという機能は次のような使用場面が想定されています。（[公式](https://graphql.org/learn/queries/#directives)から引用）
>For example, we can imagine a UI component that has a summarized and detailed view, where one includes more fields than the other.
（たとえば、一方が他方よりも多くのフィールドを含む、要約ビューと詳細ビューを持つ UIコンポーネントを想像できます。（Google翻訳））


## Mutations
`Mutation`のクエリを叩くには認証が必要になるのでこの記事では割愛します。しかしこれも`Query`の場合と大して変わらないため、ここまで読み進められた方であればすぐに理解できると思います。
## Inline Fragments
GraphQLのスキーマ定義ではユニオン型を定義できます。
例えばAniList APIの`Activity`というクエリでは戻り値として`ActivityUnion`という型が指定されており、これは次のようなユニオン型のオブジェクトです。
```graphql
union ActivityUnion = TextActivity | ListActivity | MessageActivity
```

よって`Activity`というクエリは実行するまで、上の3つの型のどれが返されるのかわからないということになります。当然型が違えばそこに定義されているフィールドも異なります。
一方でクエリは取得したいフィールドを指定して書いていました、この場合のクエリはどのように書けば良いのでしょうか？

それを解決するのがインラインフラグメントです。
これは場合分けの要領で、返ってくる型に合わせたフィールドを定義するものとなっています。
今回も先に実装例を見てみましょう。
```graphql:クエリ
query GetActivityInfo($id: Int) {
  Activity(id: $id){
    __typename
    ... on TextActivity{
      userId
      text
      siteUrl
    }
    ... on ListActivity{
      userId
      status
      media{
        title
      }
      siteUrl
    }
    ... on MessageActivity{
      id
      message
      siteUrl
    }
  }
}
```
上のクエリは、各`Activity`に紐付けられている`id`を引数にとって、その`id`に対応する`Activity`の情報を返すものとなっています。
そしてその`Activity`の型に合わせて取得するフィールドを変更しています。

まず`__typename`というフィールドでは戻り値がどの型なのかを取得できます。
クライアント側はここで取得できる情報を使うことで、処理内容を変えることができます。

そして続く`... on <オブジェクト型>`という記述では、戻り値が`<オブジェクト型>`だった場合に取得したいフィールド名を指定しています。下の例では「戻り値の型が`TextActivity`の場合には`userId`と`text`、`siteUrl`のフィールドを取得する」ということを宣言しています。
```graphql
    ... on TextActivity{
      userId
      text
      siteUrl
    }
```
他2つ（`ListActivity`、`MessageActivity`）も同様です。これでユニオン型を戻り値にもつクエリに対してもクエリが書けるようになりました。実際にクエリを実行してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/22a76af4-36a7-7281-cbc9-2b921b82f2e9.png)
無事にレスポンスが返ってきました。（ちなみにここで取得している`Activity`は私が投稿したものです。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649218/19c79be4-ee32-279f-f937-84d6b4d42409.png)

# 最後に
この記事では丁寧にGraphQLのクエリの書き方について見てきました。この記事では紹介しきれなかった機能がGraphQLにはまだまだあるので、興味がある方は[公式のドキュメント](https://graphql.org/learn/)をご覧ください。

そして株式会社HRBrainでは新しいメンバーを募集しています。

https://www.hrbrain.co.jp/recruit

とても働きやすい環境で、インターン生でありながらも新規の機能開発などをのびのびやらせてもらっています。興味がある方はぜひご応募ください。