GraphQL入門メモ 概要編


# はじめに

この記事では、GraphQL仕様やドキュメントをもとにGraphQLの基本について解説し、さらに、Go言語のライブラリである`gqlgen`を利用して実際にGraphQLサーバーを構築する方法について解説します。

この記事は、GraphQLの簡単な概要を把握したい方や、Go言語でGraphQLサーバーを構築したい方を対象とした入門記事になります。記事を通じて、GraphQLの基本的な概念や、`gqlgen`を利用したGraphQLサーバーの構築方法を学ぶことができます。

（という予定でしたがいきあたりばったりで書いていたら長くなってしまったため分割します...）

今回の記事ではGraphQLの基本について解説を行ないます。

- GraphQL入門メモ 概要編（当記事）
- GraphQL入門メモ gqlgenでGraphQLサーバー構築編

## 環境

- Go: go version go1.20.5 linux/amd64
- gqlgen: v0.17.36

# GraphQLとは

[GraphQL](https://graphql.org/)はAPI用のクエリ言語であり、定義した型システムを使用してクエリを実行するためのランタイムです。GraphQLはクライアントに必要なものだけを要求する能力を与えます。またGraphQLが特定のデータベースやストレージエンジンに縛られることはありません。

GraphQLには多くの設計原則があり、その一部を紹介します。

- **階層的**: GraphQLは階層的なクエリ構造を採用しています。この設計は、フロントエンドのビューやコンポーネントの構造に直感的に合わせられるため、クエリの作成やデータの理解が容易になります。また、データのアンダーフェッチやオーバーフェッチを防ぐ助けとなります。
- **強い型付け**: GraphQLはアプリケーション固有の型システムを採用しています。何らかのオペレーションが与えられると、ツールは実行前にその型システムのなかで構文的に正しく、かつ有効であることを保証できます。サービスはレスポンスの形状と性質について一定の保証を行なえます。
- **クライアント指定レスポンス**: GraphQLではクライアントが具体的にどのデータを必要としているかを明示的に指定できます。GraphQLを使用せずに記述されたクライアントサーバーアプリケーションの大半では、サービスがさまざまなエンドポイントから返されるデータの形状を決定します。一方、GraphQLレスポンスには、クライアントが要求したものが正確に含まれ、それ以上は含まれません。

GraphQLは規模を問わず、さまざまなプロジェクト、環境、言語で使用されています。

# gqlgenとは

[gqlgen](https://gqlgen.com/)はGo言語で簡単にGraphQLサーバーを構築できるライブラリです。

`gqlgen`はスキーマファーストのアプローチに基づいており、GraphQLスキーマ定義言語を使用することで、GraphQLサーバーのコードを自動生成します。また`gqlgen`は型の安全性を優先しています。

# GraphQL document

クライアントはGraphQLクエリ言語を使用してGraphQLサービスにリクエストを行ないます。これらのリクエストソースをドキュメントと呼びます。

[GraphQL Specification Versions October 2021 - Overview](https://spec.graphql.org/October2021/#example-85684)に掲載されているドキュメントの例を見てみましょう。

```graphql
{
  user(id: 4) {
    name
  }
}
```

このドキュメント（クエリ）は、idが4のユーザーの`name`を取得する要求を表しています。

## Comments

ドキュメント内でコメントを記述するには、`#`を使用します。この記号に続くテキストはすべてコメントとして扱われ、実際のクエリ実行時には無視されます。

```graphql
# idが4のユーザーの名前を取得する
{
  user(id: 4) {
    name
  }
}
```

## Operations

GraphQLには3つの操作タイプ（以降、オペレーション）があります。それが、`query`・`mutation`・`subscription`です。

オペレーションは以下のような形式として定義されています。

```
# OperationDefinition
OperationType [Name] [VariableDefinitions] [Directives] {
}

# OperationType
query, mutation, subscription
```

Query・Mutationオペレーションについては後の章で解説します。
Subscriptionオペレーションはデータの変更をリアルタイムに監視するためのオペレーションです。今回の記事では扱いません。

オペレーション名の記述は任意ですが、本番アプリケーションではコードを曖昧にしないためにも使用するべきとされています。

以下はQueryオペレーションに対して`HeroNameAndFriends`というオペレーション名を与えた場合のサンプルです。

```graphql
query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}
```

[出典: Queries and Mutations | GraphQL - Operation name](https://graphql.org/learn/queries/#operation-name)

オペレーション名を使用することでデバッグやサーバーサイドのロギングに役立ちます。

ドキュメントに含まれるオペレーションが1つだけで、変数を定義せず、ディレクティブを含まないクエリである場合、そのオペレーションは`query`キーワードとオペレーション名を省略したショートハンド形式で表すことができます。

```graphql
{
  user(id: 4) {
    name
  }
}
```

## Fields

> At its simplest, GraphQL is about asking for specific fields on objects.
>
> [出典: Queries and Mutations | GraphQL - Fields](https://graphql.org/learn/queries/#fields)

フィールドの中には複雑なデータや他のデータとの関係を記述しているものもあります。このデータをネストしてリクエストすることでさらに探索できます。すべてのGraphQLオペレーションは明確な形状のレスポンスを保証するために、スカラー値を返すフィールドまでの[Selection](https://spec.graphql.org/October2021/#Selection)を明示的に指定する必要があります。

[GraphQL Specification Versions October 2021 - Fields](https://spec.graphql.org/October2021/#example-100a3)に掲載されている複雑なオペレーションを見てみましょう。

```graphql
{
  me {
    id
    firstName
    lastName
    birthday {
      month
      day
    }
    friends {
      name
    }
  }
}
```

## Arguments

フィールドは概念的には値を返す関数とされており、その動作を変更する引数を受け入れることもあります。これらの引数は`(name: value)`の形式で指定されます。

この記事ではこれまでに、`user`フィールドに対して`id`引数を指定している例が登場しています。

```graphql
{
  user(id: 4) {
    name
  }
}
```

引数はどのような順序で指定しても構いません。以下2つの例は意味的には同一です。

```graphql
{
  picture(width: 200, height: 100)
}
```

```graphql
{
  picture(height: 100, width: 200)
}
```

[出典: GraphQL Specification Versions October 2021 - Arguments are unordered](https://spec.graphql.org/October2021/#sec-Language.Arguments.Arguments-are-unordered)

## Fragments

GraphQLにはドキュメント内の重複を減らすことができるフラグメントという機能があります。

[GraphQL Specification Versions October 2021 - Fragments](https://spec.graphql.org/October2021/#sec-Language.Fragments)には、あるユーザーの共通の友人や友人に関する情報を取得するためのフラグメントの例が掲載されています。

```graphql
query noFragments {
  user(id: 4) {
    friends(first: 10) {
      id
      name
      profilePic(size: 50)
    }
    mutualFriends(first: 10) {
      id
      name
      profilePic(size: 50)
    }
  }
}
```

繰り返されているフィールドをフラグメントとして抽出します。抽出したフラグメントはスプレッド演算子（`...`）を用いることで使用できます。

```graphql
query withFragments {
  user(id: 4) {
    friends(first: 10) {
      ...friendFields
    }
    mutualFriends(first: 10) {
      ...friendFields
    }
  }
}

fragment friendFields on User {
  id
  name
  profilePic(size: 50)
}
```

フラグメントは[Queries and Mutations | GraphQL - Fragments](https://graphql.org/learn/queries/#fragments)によると、異なるフラグメントを持つ多くのUIコンポーネントを1度のデータフェッチに組み合わせる必要がある場合に役立つとされています。

### Inline Fragments

GraphQLにはインラインフラグメントという機能もあります。これは、実行時の型にもとづいてフィールドを条件付きで決定するために使用されます。

```graphql
query inlineFragmentTyping {
  profiles(handles: ["zuck", "coca-cola"]) {
    handle
    ... on User {
      friends {
        count
      }
    }
    ... on Page {
      likers {
        count
      }
    }
  }
}
```

[出典: GraphQL Specification Versions October 2021 - Inline Fragments](https://spec.graphql.org/October2021/#example-10b94)

適用する型を省略すると、インラインフラグメントは囲んでいるコンテキストと同じ型であるとみなされます。

## Variables

変数はオペレーションの先頭で定義する必要があります。変数はどの引数が動的に決定されるのかを示すのに役立ちます。

以下のコードは、`JEDI`という初期値を初期値を持つ`Episode`型の変数`$episode`を宣言しています。

```graphql
query HeroNameAndFriends($episode: Episode = JEDI) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}
```

[出典: Queries and Mutations | GraphQL - Default variables](https://graphql.org/learn/queries/#default-variables)

変数には省略可能なものと必須のものがあり、上記は`Episode`型の隣に`!`がないため省略可能です。

逆に、上記のコードを改変し、型の部分を`Episode!`に変更すると以下のようなエラーが発生します。

```graphql
query HeroNameAndFriends($episode: Episode!) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}
```

```json
{
  // "episode": "JEDI"
}
```

```json
{
  "errors": [
    {
      "message": "Variable \"$episode\" of required type \"Episode!\" was not provided.",
      "locations": [
        {
          "line": 1,
          "column": 26
        }
      ]
    }
  ]
}
```

## Directives

フィールドを条件付きで含めたりスキップしたりなど、GraphQLの実行動作を変更するオプションを提供したいことがあるかもしれません。ディレクティブを使用することでこれらを提供できます。

```graphql
query Hero($episode: Episode, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name
    }
  }
}
```

```json
{
  "episode": "JEDI",
  "withFriends": true
}
```

```json
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}
```

[出典: Queries and Mutations | GraphQL - Directives](https://graphql.org/learn/queries/#directives)

GraphQLの将来のバージョンで新しい設定可能な実行機能が採用されると、それらはディレクティブを介して公開される可能性があります。また、GraphQLサービスやツールはカスタムディレクティブを提供できます。


# QueryとMutation

ここではQueryオペレーションとMutationオペレーションについて解説を行ないます。

## Queryオペレーション

GraphQLはデータ・フェッチにもっとも焦点を当てており、そのためにQueryオペレーションが用意されています。

GraphQLの特徴の1つとして、Queryオペレーションのリクエストフォーマットと、それに対するレスポンスのフォーマットが一致していることが挙げられます。
これにより、クライアントはどのようなデータが返ってくるのかを予測することが容易になります。

## Mutationオペレーション

GraphQLにはサーバー側のデータを変更する方法が用意されています。それがMutationオペレーションです。

技術的にはQueryオペレーションでデータの書き込みを引き起こす実装が可能です。しかし、データの作成、更新、削除のようなデータを変更するためのリクエストはMutationを通じて明示的に送信されるべきとされています。

Mutationがオブジェクト型を返す場合、ネストしたフィールドを要求できます。これは更新後のデータを取得するために役立ちます。

[Queries and Mutations | GraphQL - Mutations](https://graphql.org/learn/queries/#mutations)に掲載されているMutationの例を確認してみましょう。

```graphql
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}
```

```json
{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}
```

```json
{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}
```

`createReview`フィールドが更新後の`stars`と`commentary`を返していることがわかります。

MutationフィールドもQueryフィールドと同様に複数のフィールドを含めることができます。
この際、Queryフィールドは並列に実行されるのに対して、Mutationフィールドは直列に実行されます。これはMutationが副作用を持つ可能性があるためです。提供されたMutationを直列で実行することで、副作用間の競合関係を確実に防ぐことができます。

# SchemaとType System

ここではGraphQLの型システムについて見ていきたいと思います。

## Schema

スキーマはサポートする型、ディレクティブ、および各種ルートオペレーションタイプで定義されます。

スキーマ内のすべての型は一意の名前を持ち、`Scalar`型や`Introspection`型など組み込みの型と競合する名前をつけることはできません。
また、すべてのディレクティブも同様に一意の名前を持つ必要があります。
スキーマ内のすべての型とディレクティブは`__`（アンダースコア2つ）ではじまる名前をつけることはできません。

## Scalars

スカラー値は文字列や整数のようなプリミティブな値です。
GraphQLのレスポンスは階層的なツリー構造を取りますが、このツリーの葉の部分は通常、`Scalar`型になります。（列挙型や`null`の場合もあります）

GraphQLはいくつかの組み込み`Scalar`型を提供しています。

- **Int**: 符号付き32ビット整数
- **Float**: 符号付き倍精度浮動小数点数
- **String**: 人間が読める自由形式のテキストデータ
- **Boolean**: `true`か`false`
- **ID**: オブジェクトのリフェッチやキャッシュのキーに使用される一意の識別子

GraphQLは組み込みの`Scalar`型以外にも、独自のカスタムスカラー型を導入できます。

## Objects

`Scalar`型がツリー構造の葉に当たる部分に対して、`Object`型はノード（節）に相当します。

[Schemas and Types | GraphQL - Object types and fields](https://graphql.org/learn/schema/#object-types-and-fields)に掲載されている例を見てみましょう。

```graphql
type Character {
  name: String!
  appearsIn: [Episode!]!
}
```

`name`はNon-Nullの`String`型フィールドで、`[Episode!]!`は常に配列ですべての要素が`Episode`オブジェクトであるフィールドです。

`Object`型のフィールドは、`Scalar`、列挙型、別の`Object`型、`Interface`、`Union`のいずれかです。これら5つの型を基本型とした任意のラッパー型の可能性もあります。

## Input Objects

引数に対してスカラー値や列挙型を渡すことが多いかもしれませんが、より複雑な値を渡したいこともあると思います。
そのような際、とくに作成されるオブジェクト全体を渡したいようなMutationオペレーションに役立つ`Input Object`型があります。

`Input Object`型は`input`キーワードで定義できます。

```graphql
input Point2D {
  x: Float
  y: Float
}
```

[出典: GraphQL Specification Versions October 2021 - Input Objects](https://spec.graphql.org/October2021/#example-45e4e)

`Input Object`型は与えられたリテラルや変数に応じてコアーション（ここでは別の型への変換と解釈しました）が発生します。

[GraphQL Specification Versions October 2021 - Input Coercion](https://spec.graphql.org/October2021/#sec-Input-Objects.Input-Coercion)にその一例が掲載されているので確認してみましょう。
以下は任意の`a`フィールドと必須の`b`フィールドを持った型のコアーションの例です。

```graphql
input ExampleInputObject {
  a: String
  b: Int!
}
```

| リテラル値 | 変数 | コアーション値 |
|------------|-----|--------------|
| `{ a: "abc", b: 123 }` | `{}` | `{ a: "abc", b: 123 }` |
| `{ a: null, b: 123 }` | `{}` | `{ a: null, b: 123 }` |
| `{ b: 123 }` | `{}` | `{ b: 123 }` |
| `{ a: $var, b: 123 }` | `{ var: null }` | `{ a: null, b: 123 }` |
| `{ a: $var, b: 123 }` | `{}` | `{ b: 123 }` |
| `{ b: $var }` | `{ var: 123 }` | `{ b: 123 }` |
| `$var` | `{ var: { b: 123 } }` | `{ b: 123 }` |
| `"abc123"` | `{}` | `Error: Incorrect value` |
| `$var` | `{ var: "abc123" }` | `Error: Incorrect value` |
| `{ a: "abc", b: "123" }` | `{}` | `Error: Incorrect value for field b` |
| `{ a: "abc" }` | `{}` | `Error: Missing required field b` |
| `{ b: $var }` | `{}` | `Error: Missing required field b.` |
| `$var` | `{ var: { a: "abc" } }` | `Error: Missing required field b` |
| `{ a: "abc", b: null }` | `{}` | `Error: b must be non-null.` |
| `{ b: $var }` | `{ var: null }` | `Error: b must be non-null.` |
| `{ b: 123, c: "xyz" }` | `{}` | `Error: Unexpected field c` |

## List

`List`型はリスト内の各アイテムの型（アイテム型）を宣言するコレクション型です。

フィールドが`List`型を使っていることを示すには、`pets: [Pet]`のようにアイテム型を角括弧で括ります。`matrix: [[Int]]`のように入れ子にもできます。

## Non-Null

デフォルトではGraphQLのすべての型は`null`を許容します。
nullを許容しない型を宣言するには`Non-Null`型を使用します。この型はもととなる型をラップし、そのラップされた型と同じように動作します。`name: String!`のように、末尾に感嘆符を追加することで`Non-Null`型を使用できます。

`List`型と`Non-Null`型を組み合わせることでより複雑な型を表現できます。
[GraphQL Specification Versions October 2021 - Combining List and Non-Null](https://spec.graphql.org/October2021/#sec-Combining-List-and-Non-Null)にさまざまな組み合わせの例が掲載されているため確認してみましょう。

| 期待する型 | 内部の値       | コアーションの結果                   |
|-----------|---------------|-----------------------------------|
| [Int]     | [1, 2, 3]     | [1, 2, 3]                         |
| [Int]     | null          | null                              |
| [Int]     | [1, 2, null]  | [1, 2, null]                      |
| [Int]     | [1, 2, Error] | [1, 2, null] (With logged error)  |
| [Int]!    | [1, 2, 3]     | [1, 2, 3]                         |
| [Int]!    | null          | Error: Value cannot be null       |
| [Int]!    | [1, 2, null]  | [1, 2, null]                      |
| [Int]!    | [1, 2, Error] | [1, 2, null] (With logged error)  |
| [Int!]    | [1, 2, 3]     | [1, 2, 3]                         |
| [Int!]    | null          | null                              |
| [Int!]    | [1, 2, null]  | null (With logged coercion error) |
| [Int!]    | [1, 2, Error] | null (With logged error)          |
| [Int!]!   | [1, 2, 3]     | [1, 2, 3]                         |
| [Int!]!   | null          | Error: Value cannot be null       |
| [Int!]!   | [1, 2, null]  | Error: Item cannot be null        |
| [Int!]!   | [1, 2, Error] | Error: Error occurred in item     |

# Validation

## Operation Name Uniqueness

ドキュメント内のオペレーション名は一意である必要があります。

たとえば以下のドキュメントは無効です。

```graphql
query getName {
  dog {
    name
  }
}

query getName {
  dog {
    owner {
      name
    }
  }
}
```

異なるオペレーションの場合でも同様です。

```graphql
query dogOperation {
  dog {
    name
  }
}

mutation dogOperation {
  mutateDog {
    id
  }
}
```

[出典: GraphQL Specification Versions October 2021 - Operation Name Uniqueness](https://spec.graphql.org/October2021/#sec-Operation-Name-Uniqueness)

## Leaf Field Selections

選択したフィールドが`Scalar`型または列挙型の場合、そのサブフィールドは空でなければなりません。

たとえば以下のフィールドは無効です。

```graphql
fragment scalarSelectionsNotAllowedOnInt on Dog {
  barkVolume { # Int
    sinceWhen
  }
}
```

逆に、インターフェイス、ユニオン、`Object`型の場合は空であってはなりません。

```graphql
query directQueryOnObjectWithoutSubFields {
  human # Human
}

query directQueryOnInterfaceWithoutSubFields {
  pet # Pet
}

query directQueryOnUnionWithoutSubFields {
  catOrDog # CatOrDog
}
```

[出典: GraphQL Specification Versions October 2021 - Leaf Field Selections](https://spec.graphql.org/October2021/#sec-Leaf-Field-Selections)

## All Variables Used

とあるオペレーションで定義されている変数は、そのオペレーションまたはそこに含まれるフラグメントで使用されていなければなりません。
未使用の変数はバリデーションエラーになります。

# まとめ

この記事ではGraphQL仕様やドキュメントをもとにGraphQLの基本について解説を行ないました。