GraphQLのEdgeやNodeってなに？

# はじめに
こんにちは！yDogです。GraphQLについて学習する過程で私が躓いた、GraphQLに登場する**Edge**や**Node**といった謎のフィールドに関して解説していきます。

また、ここでは架空のユーザAPIを使って説明して行こうと思います。`string型`の`email`と`name`を持つ`user型`の配列、`users`フィールドを使って説明して行きます。
それでは、まずはクエリの例を見ていただきましょう。

# 例
まず、Edgeが実装されているクエリとされていないクエリを比較してみましょう。

```graphql:Edgeなし
query {
  users(offset: 3, limit: 10) {
    name
    email
  }
}
```

```graphql:Edgeあり
query {
  users(first: 10, after: "Y3Vyc29yMTIzNDU2") {
    edges {
      node {
        name
        email
      }
    }
  }
}
```

一見すると複雑で冗長な表現に見えますが、この有無で機能面に大きな差が生まれます。では、これらが何のために存在しているのかについて見ていきましょう。

# ページネーション
結論から言うと、EdgeやNodeはページネーションのために存在しています。ページネーションは、Googleの検索結果や、ブログの記事一覧のようなたくさんのデータを分割するための機能の一つです。

![Googleのページネーション.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3471738/92b0a706-f40a-9246-3b62-bbe5f83cf321.png "Googleのページネーション")

これを実装するためのデザインパターンの一つに、Cursor-based Paginationというものがあります。このパターンをGraphQL向けに洗練した、[Relay](https://relay.dev "Relay公式サイト")の提唱する[仕様](https://relay.dev/graphql/connections.htm "Relayによる仕様書")が**Relay-Style Cursor Pagination**というものです。そして、ここに**Edge**が登場します。

まずは、先ほどのEdgeがないパターンについて振り返ってみます。

# Offset-based Pagination 
```graphql:Edgeなし
query {
  users(offset: 3, limit: 10) {
    name
    email
  }
}
```

## 特徴

このパターンは**Offset-based Pagination**と呼ばれるもので、見た目から分かるとおり直感的でわかりやすい実装となっています。シンプルなユースケースや小規模なデータに適しており、スケーラビリティが求められる場合には向いていません。

### メリット

1. **実装が簡単**
    - データベースを使用している場合、SQLのクエリに`LIMIT`と`OFFSET`を追加するだけで実装できる。
2. **番号付きページネーションができる**
    - 1ページ当たりの表示数を決めておけば、offsetごとでページ管理できる。

### デメリット
1. **パフォーマンス**
    - 先述のSQLで`OFFSET`した数だけデータを読み込むため、実行速度が低下する。
2. **データの重複**
    - データが頻繁に変更・削除される場合、offsetがズレて同じデータが異なるページに重複して表示されることがある。

## まとめ
以上のように、Offset-based Paginationはシンプルで直感的な実装が可能な代わりに、大規模なデータセットや頻繁なデータ変更がある場合、パフォーマンスやデータの一貫性に問題が生じることがあります。

次に、こうした課題を解決するために設計された、Relay-Style Cursor Paginationについて見ていきましょう。この方法は、スケーラビリティが高く、効率的なページネーションを実現することができます。

# Relay-Style Cursor Pagination
まず、[はじめに](#はじめに "このページの見出し\"はじめに\"")で例示した`Edgeあり`のコードは一部のフィールドが省略されています。すべて表示した場合はこのようになります。

```graphql:Edgeあり
query {
  users(first: 10, after: "Y3Vyc29yMTIzNDU2") {
    edges {
      node {
        name
        email
      }
      cursor
    }
    pageInfo {
      endCursor
      hasNextPage
      hasPreviousPage
      startCursor
    }
    totalCount
  }
}
```

## 特徴

このパターンは**Relay-Style Cursor Pagination**と呼ばれ、先述のとおりスケーラビリティが高いデザインパターンとなっています。まずは用語の説明をしてから、実際の運用方法について解説していきましょう。

### 用語
- Connection
- Edges
- Node
- Cursor
- Page info

### Connection
**Connection**は、`usersConnection`のように、実体となるオブジェクトの後にConnectionと付けた命名がされ、型は`UsersConnection型`となります。しかし、例では`users`のようにConnectionが省略されています。[GitHub API](https://docs.github.com/ja/graphql/guides/forming-calls-with-graphql#example-query)や[Relay](https://relay.dev/graphql/connections.htm)のサンプルでも、このように省略された命名がされています。（なお、型名は省略されません）
<details><summary>サンプルコード</summary>

```graphql:GitHub
query {
  repository(owner:"octocat", name:"Hello-World") {
    issues(last:20, states:CLOSED) {
      edges {
        node {
          title
          url
          labels(first:5) {
            edges {
              node {
                name
              }
            }
          }
        }
      }
    }
  }
}
```
```graphql:Relay
{
  user {
    id
    name
    friends(first: 10, after: "opaqueCursor") {
      edges {
        cursor
        node {
          id
          name
        }
      }
      pageInfo {
        hasNextPage
      }
    }
  }
}
```
</details>

フィールドに、`edges`, `pageInfo`, `totalCount`を持ちます。`totalCount`はその名の通り、`user`の全データ数を返すフィールドです。
```graphql:Connection
type UserConnection {
  edges: [UserEdge]
  pageInfo: PageInfo
  totalCount: Int
}
```


### Edges
**Edges**は、`Connection型`のフィールドの一つで、`Edge型`をフィールドにもつ`配列`です。このクエリの場合は、`userEdge型`になります。

```graphql:Edges
type UserEdge {
  node: User
  cursor: String
}
```

型を見てわかる通り、`node`フィールドは`User型`です。つまりは、**Node**というのは実際のデータオブジェクトそのものを指します。

### Node
GraphQLには、**Global Object Identification**と呼ばれる、各オブジェクトを一意に識別できる`id`フィールドが付与されています。そして、`Node`は`id`フィールドを持つ[インターフェース](https://graphql.org/learn/schema/#interfaces "インターフェースについて")なため、GraphQLで扱うすべてのオブジェクトが`Node`インターフェースに当てはまります。
```graphql:Node
interface Node {
  id: ID!
}

type User implements Node {
  id: ID!
  name: String!
  email: String!
}
```
また、`Node`をクエリに使うことで、オブジェクトを取得することも出来ます。この際、インターフェースから型を絞り込むために、[フラグメント](https://graphql.org/learn/queries/#fragments "フラグメントについて")を使用します。
```graphql
{
  node(id: "MTIzNDU2") {
    id
    ... on User {
      name
      email
    }
}
```

### Cursor
`cursor`は、`String型`で、各データの位置を示します。データの位置が分かることで、ページネーションの制御を効率的にできます。
```graphql
{
  users(first: 10, after: "Y3Vyc29yMTIzNDU2")
}
```
例えば、上記の`after`の引数は`cursor`となっています。
この場合の返り値は、引数の`cursor`の次のデータからになります。

これにより、Offset-based Paginationのように`OFFSET`で指定した数だけクエリを投げる必要がなくなり、パフォーマンスへの影響が大きく減ります。

:::note info
なお、一般にCursorの値はIDや作成日時などのデータを元にエンコードされた文字列なため、サーバで復号してからSQLクエリが生成されます。
:::

ここまでで、タイトルの**GraphQLのEdgeやNodeってなに？** の回答が出ました。
**Edge**は`node`と`cursor`を持つフィールド、**Node**はデータそのもの、ついでに**Cursor**はデータの位置を表していることが分かりました。

それでは最後に、ページネーションをさらに効率的にしてくれる、**Page info**について見ていきましょう。

### Page info

Page infoは、ページネーションを円滑にするためのメタデータを含んだフィールドです。
```graphql:Page info
type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}
```
`has~~`は名前の通り、このクエリでのデータの前後にデータがあるかを示し、`~~Cursor`は、現在のクエリの最初と最後のデータのCursorが記述されています。

## まとめ
Relay-Style Cursor Paginationは、Cursorを用いた効率的でスケーラブルな方式で、従来のOffset-based Paginationに比べ、サーバーの負荷を抑え、効率的なデータ管理を実現します。

# 感想
ここまで読んできた皆様ならお分かりだと思いますが、学習当初の私は、実際に重要なのは**Edge**や**Node**ではなく、**Cursor**なのだと気付きませんでした。その結果、**Relay-Style Cursor Pagination**という単語にたどり着くのが遅くなり、無駄な時間を過ごしてしまうこととなりました。
特に、こちらの[記事](https://zenn.dev/hsaki/articles/github-graphql "ここさえ抑えればGitHub API v4がわかる! GraphQL入門")のおかげでGraphQLへの理解が一気に深まりました。この場をお借りしてH.Sakiさんへ感謝の言葉を伝えたいと思います。本当にありがとうございました。

# 参考リンク

https://graphql.org/learn/pagination/


https://docs.github.com/ja/graphql/overview/explorer

https://relay.dev

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

https://www.apollographql.com/docs/react/pagination/overview/