10分で理解する GitHub GraphQL API (Query)

# はじめに

初めて GraphQL API(query) を使う人が、GitHub GraphQL API の公式ドキュメント読んだだけでは確実につまづくだろうなぁというポイントをまとめています。

あくまで大枠を理解してもらうことに主眼を置いているので、細かい仕様などは説明しきれていませんがご了承ください。


# 記事のゴール

「GraphQLってなに？」という人が、データ取得のクエリを作成できるようになること

※ 本記事では `Mutation` については扱いません


# 参考資料

https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#example-query

# もくじ

- [GraphQL APIの概要](#graphql-apiの概要)
  - [GraphQLについて](#graphqlについて)
  - [GitHub Rest API（v3）との違い](#github-rest-apiv3との違い)
  - [GitHub GraphQLのオペレーション種類](#github-graphqlのオペレーション種類)
- [クエリ操作について](#クエリ操作について)
  - [クエリの例](#クエリの例)
  - [GraphQL APIに初めて触れた人が直面する疑問](#graphql-apiに初めて触れた人が直面する疑問)
  - [公式ドキュメントのサンプルコードを１行ずつ補足します](#公式ドキュメントのサンプルコードを１行ずつ補足します)
  - [① Query タイプのオペレーション宣言](#-query-タイプのオペレーション宣言)
  - [② Query タイプが持つ repository フィールドへのクエリ](#-query-タイプが持つ-repository-フィールドへのクエリ)
  - [③ repository フィールドが持つ issues フィールドへのクエリ](#-repository-フィールドが持つ-issues-フィールドへのクエリ)
  - [④ Issues フィールドが持つ edges フィールドへのクエリ](#-issues-フィールドが持つ-edges-フィールドへのクエリ)
  - [⑤ edges フィールドが持つ node フィールドへのクエリ](#-edges-フィールドが持つ-node-フィールドへのクエリ)
  - [⑥ node フィールドが持つ各フィールドへのクエリ](#-node-フィールドが持つ各フィールドへのクエリ)
  - [⑦ labels フィールドが持つ各フィールドへのクエリ](#-labels-フィールドが持つ各フィールドへのクエリ)
  - [Example query を振り返る](#example-query-を振り返る)
- [GitHub GraphQL API(query) の全体を把握する](#github-graphql-apiquery-の全体を把握する)
- [おわりに](#おわりに)

# GraphQL APIの概要

## GraphQLについて

[GraphQL](https://graphql.org/) は Facebook によって開発された API のためのクエリ言語です。従来の REST API では各エンドポイントでサーバーが返すデータを決定していたのに対し、**GraphQL は単一のエンドポイントを通じてデータの取得や操作を可能にします**。

つまり、クライアント側で必要なデータ構造のクエリを定義し、それをサーバーに送信することで、必要な情報だけを取得できるというわけです。

イメージとして、「SQL とそっくり」と捉えてもらえばけっこうしっくりくるんじゃないかなと思っています。（SQL は RDB に対するクエリ言語ですが、GraphQL は API のためのクエリ言語です！）

そんなこんなで、GitHub でも REST API だけでなく GraphQL API が使えるようになっています。

> GitHubは、インテグレーターにとって大きな柔軟性を提供してくれることから、GraphQLを選択しました。 必要—なデータを正確に定義する機能と、必要_なデータ_のみを—定義できることは、従来の REST API エンドポイントよりも強力な利点です。 GraphQL を使用すると、複数の REST 要求を 1 回の呼び出しに 置き換えて、指定したデータをフェッチできます。

参考：[GitHub が GraphQL に投資した理由について書かれたブログ](https://github.blog/developer-skills/github/the-github-graphql-api/)


## GitHub Rest API（v3）との違い

Rest API と比較したときの GraphQL の特徴・利点は、**より柔軟にデータ取得ができること**、とだけ覚えてればOKかと思います。

- `REST API(v3)`：
  - 固定のエンドポイントからデータを取得するので、事前に決定されている構造でデータが返される
    - 1 度に必要以上のデータを取得する「オーバーフェッチング」が起きやすい
    - 必要なデータを 1 度に取得できない「アンダーフェッチング」が起きやすい
  - 上記より、各エンドポイントに対して投げるリクエストの数が増えやすい
- `GraphQL API(v4)`：
  - クライアントが必要なデータの構造をクエリとして送信するため、必要なデータを過不足なく取得できる
  - 上記が最大の特徴であり利点だが、REST API とは全く違うアプローチをとっているので学習コストが高め

https://docs.github.com/ja/rest/about-the-rest-api/comparing-githubs-rest-api-and-graphql-api?apiVersion=2022-11-28

## GitHub GraphQLのオペレーション種類

一般的に GraphQL では以下の 3 種類の操作が定義されていますが、GitHub GraphQL API はそのうちの 2 種類をサポートしています。

> The two types of allowed operations in GitHub's GraphQL API are queries and mutations.

- `Query`：
  - データを取得する（読みとる）ための操作
  - REST API の `GET` リクエストのようなもの
- `Mutation`：
  - データの作成、編集、削除など、データを更新するための操作
  - REST API の `POST` / `PUT` / `DELETE` リクエストのようなもの
- `Subscription`：
  - リアルタイムでデータの変更を受け取るための操作
  - データを継続的に取得する際などに使われる
  - 現時点で GitHub GraphQL API ではサポートされていない

https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#about-query-and-mutation-operations


# クエリ操作について

ここから本題の GitHub GraphQL API の `Query` 操作について解説します。

## クエリの例

公式ドキュメントの [Example query](https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#example-query) で利用されているクエリをそのまま使いつつ解説していきます。

```graphql: GitHub公式ドキュメント内の Example query
query {
  repository(owner:"octocat", name:"Hello-World") {
    issues(last:20, states:CLOSED) {
      edges {
        node {
          title
          url
          labels(first:5) {
            edges {
              node {
                name
              }
            }
          }
        }
      }
    }
  }
}
```

上記をコマンドで実行する場合は以下のようになります。

```terminal: 公式ドキュメントのExample queryを実行するコマンド
curl -X POST -H "Authorization: bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" --data '{ "query": "query GetLast20ClosedIssues { repository(owner: \"octocat\", name: \"Hello-World\") { issues(last: 20, states: CLOSED) { edges { node { title url labels(first: 5) { edges { node { name } } } } } } } }" }' https://api.github.com/graphql
```

また、上記の実行結果は以下のようになります。

<details><summary> 【click to expand】 ※ Example query の実行結果 </summary>

```json: result.json
{
    "data": {
        "repository": {
            "issues": {
                "edges": [
                    {
                        "node": {
                            "title": "Internationalization",
                            "url": "https://github.com/octocat/Hello-World/issues/2831",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "SydScreenShot_FullPage.png (972×6654)",
                            "url": "https://github.com/octocat/Hello-World/issues/2856",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "Hello, world!2",
                            "url": "https://github.com/octocat/Hello-World/issues/2857",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "Hello, world!",
                            "url": "https://github.com/octocat/Hello-World/issues/2858",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "Hello, world!",
                            "url": "https://github.com/octocat/Hello-World/issues/2859",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "test",
                            "url": "https://github.com/octocat/Hello-World/issues/2861",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "https://github.com/octocat/Hello-World/issues/2874#issue-2054638316",
                            "url": "https://github.com/octocat/Hello-World/issues/2883",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "El disciplina",
                            "url": "https://github.com/octocat/Hello-World/issues/2885",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "take a test",
                            "url": "https://github.com/octocat/Hello-World/issues/3004",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": " octocat/Hello-World",
                            "url": "https://github.com/octocat/Hello-World/issues/3013",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "Hello",
                            "url": "https://github.com/octocat/Hello-World/issues/3021",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "Found a bug",
                            "url": "https://github.com/octocat/Hello-World/issues/3056",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "test linked issue",
                            "url": "https://github.com/octocat/Hello-World/issues/3057",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "Hello world",
                            "url": "https://github.com/octocat/Hello-World/issues/3067",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "Hello, world!",
                            "url": "https://github.com/octocat/Hello-World/issues/3103",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "Dfr",
                            "url": "https://github.com/octocat/Hello-World/issues/3127",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "01-10-Tree",
                            "url": "https://github.com/octocat/Hello-World/issues/3161",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "https://developer.grammarly.com/API KEY QfXE9AHW2PdtmBDO6ppHXsM1",
                            "url": "https://github.com/octocat/Hello-World/issues/3168",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "N",
                            "url": "https://github.com/octocat/Hello-World/issues/3183",
                            "labels": {
                                "edges": []
                            }
                        }
                    },
                    {
                        "node": {
                            "title": "Bug: Issue with loading the homepage",
                            "url": "https://github.com/octocat/Hello-World/issues/3202",
                            "labels": {
                                "edges": []
                            }
                        }
                    }
                ]
            }
        }
    }
}
```

</details>


## GraphQL APIに初めて触れた人が直面する疑問

『こんな感じで取得できるんか便利そうだね！』

『**────んで、このクエリどうやって作った...????????????**』

『公式ドキュメントでクエリの例って説明されてるけど、いまいちよく分からんな...。』

## 公式ドキュメントのサンプルコードを１行ずつ補足します

おそらくこんな動機があったのでしょう。

『 `octocat/Hello-World` リポジトリ内で、解決された issue のうち最新の 20 個を取得し、各 issue のタイトル、URL、最初の 5 つのラベルを見たいなぁ！！』

というわけで、以下でクエリを 1 行ずつ解説していきます。

![はじめてのGitHub-GraphQL_全体.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3388832/11d0eca0-b666-1fe7-3112-950d8861ae0d.png)


- [① Query タイプのオペレーション宣言](#-query-タイプのオペレーション宣言)
- [② Query タイプが持つ repository フィールドへのクエリ](#-query-タイプが持つ-repository-フィールドへのクエリ)
- [③ repository フィールドが持つ issues フィールドへのクエリ](#-repository-フィールドが持つ-issues-フィールドへのクエリ)
- [④ Issues フィールドが持つ edges フィールドへのクエリ](#-issues-フィールドが持つ-edges-フィールドへのクエリ)
- [⑤ edges フィールドが持つ node フィールドへのクエリ](#-edges-フィールドが持つ-node-フィールドへのクエリ)
- [⑥ node フィールドが持つ各フィールドへのクエリ](#-node-フィールドが持つ各フィールドへのクエリ)
- [⑦ labels フィールドが持つ各フィールドへのクエリ](#-labels-フィールドが持つ各フィールドへのクエリ)


## ① Query タイプのオペレーション宣言

![はじめてのGitHub-GraphQL_query.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3388832/5231f9cb-daac-e924-9b09-1a603553ca08.png)

> - `query {`
Because we want to read data from the server, not modify it, `query` is the root operation. (If you don't specify an operation, `query` is also the default.)

[GitHub GraphQLのオペレーション種類](#github-graphqlのオペレーション種類) で説明しましたが、GitHub GraphQL API では以下の２つのオペレーションをサポートしています。

- `Query`：データを取得する（読む）ための操作
- `Mutation`：データを作成、編集、削除など、データを更新するための操作

今回の GraphQL を利用する動機が「最新の issue を見たい」なので、利用するのは `Query` オペレーションです。

クエリの頭で `query` と宣言していますが、**GraphQL のサーバーが処理を行うための起点になるため、オペレーションの宣言はクエリ内に必ず含める必要があります。**

また、クエリに名前を付けることも可能です。

```terminal: 公式ドキュメントのQueryにFetchRecentClosedIssuesという名前を付けました
curl -X POST -H "Authorization: bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" --data '{ "query": "query FetchRecentClosedIssues { repository(owner: \"octocat\", name: \"Hello-World\") { issues(last: 20, states: CLOSED) { edges { node { title url labels(first: 5) { edges { node { name } } } } } } } }" }' https://api.github.com/graphql
```


## ② Query タイプが持つ repository フィールドへのクエリ

![はじめてのGitHub-GraphQL_repository.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3388832/9a8ff336-3b8f-44d1-3cf0-92391b4cb0b2.png)

> - `repository(owner:"octocat", name:"Hello-World") {`
To begin the query, we want to find a `repository` object. The schema validation indicates this object requires an `owner` and a `name` argument.


日本語版では、「クエリを開始するには、repository オブジェクトを検索します。 スキーマの検証により、このオブジェクトに owner と name の引数が必要であることがわかります。」とありますが、初見ではまぁ意味がわかりません。

そこで、まず見るべきドキュメントはコチラになります。

https://docs.github.com/en/graphql/reference/queries

> The query type defines GraphQL operations that retrieve data from the server.

GitHub GraphQL Query の中には、クエリの中の大分類ともいえるいくつかの種類が用意されているのですが、ここでは「Queryの中でもどういったQuery？」を宣言することで絞りを入れるようなイメージです。

公式ドキュメントの Example query の例では「`octocat/Hello-World` リポジトリ内で、解決された issue」を取得したいので、[`repository`](https://docs.github.com/en/graphql/reference/queries#repository) を選択しています。

さて、`repository` のドキュメントを確認すると「`Arguments for repository`」で必要な引数が記載されています。

- `followRenames (Boolean)`：デフォルトで `true`
- `name (String!)`：リポジトリ名
- `owner (String!)`：リポジトリのオーナー名（ユーザー名または組織名）

必須なのは `owner` と `name` なので、`octocat/Hello-World` リポジトリに対するクエリの場合はサンプルの通り、 `  repository(owner:"octocat", name:"Hello-World") {` になるというわけですね。

https://github.com/octocat/Hello-World


## ③ repository フィールドが持つ issues フィールドへのクエリ

![はじめてのGitHub-GraphQL_issues.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3388832/1b7e04e0-f860-19cf-24ef-e275e3302dc3.png)

> - `issues(last:20, states:CLOSED) {`
To account for all issues in the repository, we call the `issues` object. (We could query a single `issue` on a `repository`, but that would require us to know the number of the issue we want to return and provide it as an argument.)

先ほどの [② Query タイプが持つ repository フィールドへのクエリ](#-query-タイプが持つ-repository-フィールドへのクエリ) で `repository` クエリを選択したことは分かりました。次の関心事は、「リポジトリ情報からどの情報を引っ張ってくるか？」です。

そのためにまず、この `Repository` クエリがどんなオブジェクトを返すのかを知る必要があります。

方法は２つあり、

- さっきの [`repository` クエリのドキュメント](https://docs.github.com/ja/graphql/reference/queries#repository)を見る
- クエリを投げる（[Discovering the GraphQL API](https://docs.github.com/en/graphql/guides/introduction-to-graphql#discovering-the-graphql-api)参照）

です。

<details><summary>【click to expand】※クエリを投げる場合はこんな感じで確認できます</summary>

```terminal: repositoryクエリが返すオブジェクトを特定するクエリ
curl -X POST -H "Authorization: bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" --data '{ "query": "query { __type(name: \"Repository\") { name kind description fields { name } } }" }' https://api.github.com/graphql
```

`Repository` という名前のオブジェクトが返ることが分かります。

```json: 戻ってくるJSONオブジェクト
{
    "data": {
        "__type": {
            "name": "Repository",
            "kind": "OBJECT",
            "description": "A repository contains the content for a project.",
            "fields": [
                {
                    "name": "allowUpdateBranch"
                },
                {
                    "name": "archivedAt"
                },
                {
                    "name": "assignableUsers"
                },
                {
                    "name": "autoMergeAllowed"
                },
                {
                    "name": "branchProtectionRules"
                },
                ※以下略
```

</details>

というわけで、 `repository` クエリは `repository` オブジェクトを返すことが分かりました。そこが分かったら、`repository` オブジェクトに関するドキュメントを見てみましょう。

https://docs.github.com/ja/graphql/reference/objects#repository

ドキュメントを確認すると、`repository` オブジェクトはたくさんのフィールドを持っていることが分かります。

今回は「解決された issue のうち最新の 20 件」が欲しいので、個別の Issue である `issue` フィールドではなく `issues` フィールドを選択します。

以下で、公式ドキュメントの [クエリの例](https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#example-query) で説明されている `Some details about the issues object:` の部分をいい感じに翻訳します。

> - The docs tell us this object has the type `IssueConnection`.

訳：`issues` というフィールドが `IssueConnection` 型を返すことを意味しており、`IssueConnection` 型はnon-nullable（このフィールドがnullを返すことはない！）であることを示しているよ。

![はじめてのGitHub-GraphQL_IssueConnection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3388832/9b5f8443-5462-8933-8af9-2e8ed655d7e6.png)


※ GraphQLの型システムでは、型名の後に `!` が付くことで、その型がnon-nullableであると指定されます

> - Schema validation indicates this object requires a `last` or `first` number of results as an argument, so we provide `20`.

訳：`Arguments for issues` に記載されている引数を指定することで、絞りをかけられるよ。今回だったら「Issue のうち最新の20件」が欲しいから、`last:20` で指定するよ。


> - The docs also tell us this object accepts a `states` argument, which is an `IssueState` enum that accepts `OPEN` or `CLOSED` values. To find only closed issues, we give the `states` key a value of `CLOSED`.

訳：`Arguments for issues` では `states` も引数で指定できるって書いてあるよ。「解決済み Issues」の情報が欲しいので、`states:CLOSED` を指定するよ。


## ④ Issues フィールドが持つ edges フィールドへのクエリ

![はじめてのGitHub-GraphQL_edges1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3388832/82559527-0cf1-1048-5d3f-01aa460debe2.png)

> - `edges {`
We know `issues` is a connection because it has the `IssueConnection` type. To retrieve data about individual issues, we have to access the node via `edges`.

上記の説明文を１つずつ解読します。

> We know issues is a connection because it has the IssueConnection type.

GraphQL には `connection` という概念があります。

GitHub GraphQL API での 1 度のクエリリクエストにより、特定の `connection` オブジェクト（例えば `IssueConnection` ）が 1 つ得られます。`connection` オブジェクトはそれに関連する複数のアイテム（この場合は Issue の）情報をリストで持っています。（例えば `edges` フィールドや `nodes` フィールドなど）

詳しくはこちらの記事を読むとイメージが付くかと思います。（図解がとても分かりやすくてありがたい）

https://zenn.dev/hsaki/articles/github-graphql#%E3%83%9A%E3%83%BC%E3%82%B8%E3%83%8D%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E8%80%83%E3%81%88%E6%96%B9

なので、とりあえず「`issues` フィールドの戻り値は `IssueConnection` 型ってことは、Issue に関する `connection` オブジェクトを介してデータを取得することができるんだな」くらいの理解でいったんよいと思います。

> To retrieve data about individual issues, we have to access the node via edges.

「個々の issue に関するデータを取得するには、`edges` を介して `node` にアクセスする必要があるよ」とのことですが、実は Example query の場合は `edges` を介さなくても同じデータを取得することができます。

例えば、`IssueConnection` オブジェクトは以下のように `edges`、`nodes`、`pageInfo`、`totalCount` と４つのフィールドを持っています。

```json: IssueConnectionの情報
{
    "data": {
        "__type": {
            "name": "IssueConnection",
            "kind": "OBJECT",
            "description": "The connection type for Issue.",
            "fields": [
                {
                    "name": "edges",
                    "args": []
                },
                {
                    "name": "nodes",
                    "args": []
                },
                {
                    "name": "pageInfo",
                    "args": []
                },
                {
                    "name": "totalCount",
                    "args": []
                }
            ]
        }
    }
}
```

GitHub公式ドキュメント内の Example query では上記の `edges` フィールドを使っていますが、以下のような `nodes` フィールドを使う形でも同じデータが取れます。

```graphql: GitHub公式ドキュメント内の Example query のedges使わないバージョン
query {
  repository(owner:"octocat", name:"Hello-World") {
    issues(last:20, states:CLOSED) {
      nodes {
        title
        url
        labels(first:5) {
          nodes {
            name
          }
        }
      }
    }
  }
}
```

なのですが、なぜここで 「個々の issue に関するデータを取得するには `edges` を介してね」と言われているかというと、簡単に言えば今回のような `IssueConnection` オブジェクト（つまりはIssueの一覧）を操作する際に、`edges` を介したほうが融通が利きやすいからです。

今回の例からは話が逸れますが、例えば直近に起票された Issue とその関連情報を 500 件取得したいとして、1 度のリクエストで 100 件分を取得しようとすると、リクエストを 5 回投げる必要があります。このとき、100 件目と 101 件目をどう判別したらよいでしょうか。

`IssueConnection` オブジェクトのような複数の Issue を内包したオブジェクトにおいて、`edges` は各 `node` （つまり個々のIssue）への参照だけでなく、各 `node` がオブジェクト全体の中でどの位置にいるかという追加情報（ページネーション用の `cursor` など）を提供します。

Issue が多数ある場合、全てを一度にロードしてクライアントに返すのは効率が悪い（もしかしたら不可能かも）場合があるので、GraphQL では `edges` を通じてそれぞれの `node` にアクセスし、各 `edge` が持つ `cursor` を利用して、どのデータが既に取得されたかを追跡しながらページネーションを効果的に実施できるようになっています。

まとめるとこんな感じです。

- `edges` フィールドはページネーションや `node` のメタデータが必要な場合に便利
- `nodes` フィールドは直接 `node` にアクセスするようなシンプルなデータ取得の場合に効率的

話を戻すと、個別の Issue に関わる周辺情報などを取得する場合は、 `edges` を介してデータ取得すると融通利くよ！ということが分かっていればよい気がします。


## ⑤ edges フィールドが持つ node フィールドへのクエリ

![はじめてのGitHub-GraphQL_node1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3388832/579d4fca-b085-e99b-022c-5fc3ddbccea0.png)

> `- node {`
Here we retrieve the node at the end of the edge. The `IssueConnection` docs indicate the node at the end of the `IssueConnection` type is an `Issue` object.

`IssueConnection` フィールドが持つ `edges` フィールドは、リスト型の `IssueEdge` オブジェクトを返します。で、`IssueEdge` オブジェクトはノードとして、欲しかった Issue 情報である `Issue` オブジェクトを持っていることが分かります。


https://docs.github.com/ja/graphql/reference/objects#issueedge

## ⑥ node フィールドが持つ各フィールドへのクエリ

![はじめてのGitHub-GraphQL_issue_fields.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3388832/987f9ba6-1bf2-6784-c7eb-f32d0da1c9b9.png)

> - Now that we know we're retrieving an `Issue` object, we can look at the docs and specify the fields we want to return:
Here we specify the `title`, `url`, and `labels` fields of the Issue object.

はい、本命の Issue の情報までたどり着きました。

https://docs.github.com/en/graphql/reference/objects#issue

もともとの動機が『 `octocat/Hello-World` リポジトリ内で、解決された issue のうち最新の 20 個を取得し、各 issue のタイトル、URL、最初の 5 つのラベルを見たいなぁ！！』だったので、`title`、`url`、`label` を探します。結果、以下のような戻り値であると分かりました。

- title (String!)
- url (URI!)
- labels (LabelConnection)

今まで「このフィールドはこのオブジェクトを返す」で辿ってきましたが、どこまでいけば終わりが来るのでしょうか。その答えは以下のドキュメントにまとまっています。

https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#about-queries

> GraphQL queries return only the data you specify. To form a query, you must specify fields within fields (also known as nested subfields) until you return only scalars.

https://docs.github.com/en/graphql/reference/scalars

> Scalars are primitive values: Int, Float, String, Boolean, or ID.
> When calling the GraphQL API, you must specify nested subfields until you return only scalars.

GraphQLにおいて、スカラー（`Scalar`）はプリミティブな値を表しており、ドキュメントでは  `Int`、`Float`、`String`、`Boolean`、`ID` がそれにあたると示されています。なので、それ以上分解できない単一の値（スカラー）になるまで、つまり戻り値が `Int`、`Float`、`String`、`Boolean`、`ID` になるところまで指定しないといけないということですね。

これを踏まえて、上記の `title`、`url`、`labels` フィールドを見てみます。

- `title` (`String!`)：`String` なのでここが末端
- `url` (`URI!`)：[ドキュメント](https://docs.github.com/en/graphql/reference/scalars#uri) より形式が指定された `String` なのでここが末端 
- `labels` (`LabelConnection`)：`Connection` オブジェクトなのでさらに深堀りが必要になる

もうちょっとがんばりましょう。


## ⑦ labels フィールドが持つ各フィールドへのクエリ

![はじめてのGitHub-GraphQL_labels.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3388832/6c1d9b90-c409-5365-ea00-ee8f33118945.png)

> The `labels` field has the type `LabelConnection`. As with the `issues` object, because `labels` is a connection, we must travel its edges to a connected node: the `label` object. At the node, we can specify the `label` object fields we want to return, in this case, `name`.

もはや IssueConnection と同じです。

`labels` (`LabelConnection`) > `edges` (`[LabelEdge]`) > `node` (`Label`) > `name` (`String!`) の順番に進んでいくと、最終的にプリミティブな値であるスカラー（`String`）にたどり着きました。

https://docs.github.com/en/graphql/reference/objects#label

## Example query を振り返る

クエリを 1 行ずつ上から見てきましたが、いかがでしたでしょうか。

![はじめてのGitHub-GraphQL_全体.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3388832/e8eb21b5-0e98-ffe3-23e4-f7202ddaa0ee.png)

ドキュメントの例ではクエリ型の選択という大きいところから出発して開設されていますが、実際にクエリを作成する際にはほしいデータからの逆算で考えるとよいかもしれません。

「戻り値がスカラーである取得したいフィールド」から逆算して、そのフィールドがどのオブジェクトに属しているのかを 1 つずつ見ていきながらクエリを作成するという感じになるかもです。


# GitHub GraphQL API(query) の全体を把握する

GraphQL はデータを要求し取得するための強力なクエリ言語ですが、その効率と柔軟性の秘密は「スキーマ」にあります。

スキーマは GraphQL API を利用する上での地図のようなもので、スキーマへの理解があれば、自分でクエリを作成していくことができるようになります。

https://docs.github.com/en/graphql/overview/public-schema

以下は、公式が提供している `schema.docs.graphql` という GraphQL を利用するうえで必要になるスキーマ情報から、今回解説した Example query に関わる部分を抜き出したものです。

```graphql: schema.docs.graphqlから今回見た部分を抜き出したもの
type Query {
  repository(
    followRenames: Boolean = true
    name: String!
    owner: String!
  ): Repository
}

type Repository implements Node & PackageOwner & ProjectOwner & ProjectV2Recent & RepositoryInfo & Starrable & Subscribable & UniformResourceLocatable {
  issues(
    after: String
    before: String
    filterBy: IssueFilters
    first: Int
    labels: [String!]
    last: Int
    orderBy: IssueOrder
    states: [IssueState!]
  ): IssueConnection!
}

type IssueConnection {
  edges: [IssueEdge]
  nodes: [Issue]
  pageInfo: PageInfo!
  totalCount: Int!
}

type IssueEdge {
  cursor: String!
  node: Issue
}

type Issue implements Assignable & Closable & Comment & Deletable & Labelable & Lockable & Node & ProjectV2Owner & Reactable & RepositoryNode & Subscribable & SubscribableThread & UniformResourceLocatable & Updatable & UpdatableComment {
  labels(
    after: String
    before: String
    first: Int
    last: Int
    orderBy: LabelOrder = {field: CREATED_AT, direction: ASC}
  ): LabelConnection
  title: String!
  url: URI!
}

type LabelConnection {
  edges: [LabelEdge]
  nodes: [Label]
  pageInfo: PageInfo!
  totalCount: Int!
}

type LabelEdge {
  cursor: String!
  node: Label
}

type Label implements Node {
  name: String!
}
```

例えば、

- `type Query` は `repository` フィールドを持っており、`repository` フィールドに対するクエリは `repository` 型のオブジェクトを返す
- `type repository` は `issues` フィールドを持っており、`issues` フィールドに対するクエリは `IssueConnection!` 型のオブジェクトを返す
- `type IssueConnection ` は `edges` フィールドを持っており、`edges` フィールドに対するクエリは リスト型の `IssueEdge` オブジェクトを返す

といったように、スキーマは入れ子構造のような関係になっていることが分かります。つまり、スキーマを 1 つずつ辿っていくことで、取得したいデータのクエリを組み立てることができるというわけです。

https://note.com/mtng420/n/ndf5d579c6183


# おわりに

ここで紹介したものはあくまで基礎の部分ですが、この辺を分かっていれば関連情報を調べるときに理解しやすくなるかなと思います。

今回の記事では紹介していませんが、インターフェースやエイリアス、Fragment など周辺の概念なども知っておくとより扱える幅が広がります。

世の中にはいっぱい記事があるので、もし興味を持ったら手を動かしてみてください~！