FlutterでGraphQLとgraphql_codegenを使ってみたので簡単にまとめてみた！

## **GraphQL**

https://graphql.org/


https://pub.dev/packages/graphql_flutter

今回は業務の中でGraphQLを使用する機会があったので、簡単にまとめてみました！

### 記事のターゲット

- graphql_codegenを使ってみたい人
- GraphQLの基礎を学びたい人

### この記事に記載されていないこと

- Fragmentについて（今後の記事で扱うかも）
- 自動生成を行わないで実装する方法

## **GraphQLとは**

GraphQLとは、APIを作成し、クライアントがサーバーからデータを取得するためのクエリ言語です。簡単に言えば、クライアントとサーバー間でデータを効率的にやり取りする方法です。

## **GraphQLとREST APIの違い**

GraphQLとよく比較されるのがREST APIです。REST APIとGraphQLの主な違いは以下の通りです。

| 特徴 | REST API | GraphQL |
| --- | --- | --- |
| エンドポイント | 複数のエンドポイント | 単一のエンドポイント |
| データ取得 | 固定されたレスポンス | 必要なデータのみを指定して取得 |
| データ操作 | HTTPメソッド（GET, POST, PUT, DELETE） | クエリとミューテーション |
| パフォーマンス | 過剰なデータの送受信が発生する場合がある | 必要なデータのみを取得し効率的 |
| 柔軟性 | 固定されたデータ構造 | 柔軟にデータ構造を指定可能 |

私がGraphQLを使用して最も良いと感じる点は、データ取得の際に必要なデータのみを指定して取得できることです。

例えば、ユーザー1とユーザー2を表示する画面で、ユーザーにはそれぞれ**名前、生年月日、好きな食べ物**が設定されているとします。このユーザーデータを取得する場合を考えます。

**REST APIの場合：**

ユーザー1とユーザー2の名前、生年月日、好きな食べ物の全てのデータを取得します。

**GraphQLの場合：**

ユーザー1は名前、生年月日、好きな食べ物の全てのデータを取得し、ユーザー2は名前と生年月日のみを指定して取得することが可能です。

:::note info
GraphQLの場合はユーザー1とユーザー2で取得するデータをこちらから柔軟に指定できます:thumbsup:
:::

## **GraphQLの基本概念**

### クエリ (Query)

クエリとは、クライアントがサーバーから必要なデータを取得するためのリクエストのことです。

**例:** あるアプリで、ユーザーのリストを表示する画面があるとします。この画面を表示するために、クライアント(アプリ側)はサーバーに「全てのユーザー情報をください」とリクエストを送ります。これがクエリです。

```graphql
query GetUsers {
  users {
    id
    name
  }
}
```

### ミューテーション (Mutation)

ミューテーションとは、サーバー上のデータを変更するための操作です。

**例:** あるアプリで、新しいユーザーを登録するフォームがあるとします。ユーザーがフォームに情報を入力して「登録」ボタンを押すと、クライアント(アプリ側)はサーバーに「新しいユーザーを追加してください」とリクエストを送ります。これがミューテーションです。

```graphql
mutation AddUser($name: String!) {
  addUser(name: $name) {
    id
    name
  }
}
```

### スキーマ (Schema)

スキーマとは、APIのデータ構造とそのデータがどのように操作されるかを定義するものです。

**例:** レストランのメニューがスキーマだとしましょう。メニューには、提供される料理の名前、価格、成分などが記載されています。これを見れば、どんな料理が注文できるのかがわかります。同じように、スキーマを見ると、そのAPIがどんなデータを提供し、どんな操作ができるのかがわかります。

```graphql
type User {
  id: ID!
  name: String!
}

type Query {
  users: [User!]!
}

type Mutation {
  addUser(name: String!): User!
}
```

## **二つの型（input型とtype型）**

### `type`型

`type`型は、サーバーからクライアントに返されるデータを定義します。つまり、クエリやミューテーションのレスポンスとして使用されます。例えば、ユーザー情報を返す場合の構造を定義します。

**例:**

```graphql
type User {
  id: ID!
  name: String!
  email: String!
}
```

### `input`型

`input`型は、クライアントからサーバーに送信されるデータの構造を定義します。主にミューテーションの引数として使用されます。例えば、新しいユーザーを追加する際に送信するデータの構造を定義します。

**例:**

```graphql
input CreateUserInput {
  name: String!
  email: String!
}
```

### なぜ `type` と `input` を区別する必要があるのか

GraphQLでは、`type`と`input`を混在させないようにする必要があります。これにより、データの送受信時に構造が明確になり、予期しないエラーを防ぐことができます。

:::note alert
`type`フィールドに他の`type`を参照することはできますが、`input`フィールドに`type`を直接参照することはできません:weary:
:::

### 間違った例とエラー例

初学者として、このルールを知らずに、`input`タイプの中で`type`を参照しようとしてエラーになった例を示します。

```graphql
input CreateUserInput {
  name: String!
  email: String!
  profile: UserProfile # inputにtypeを定義しているから間違い
}

type UserProfile {
  bio: String!
  avatarUrl: String!
}
```

上記のように、`input`の中で`type`を参照することはできません。これを実行してしまうと、上手く通信ができなかったり、自動生成を使っている場合は生成に失敗します。
以下のようなエラーが表示されます。

```plaintext
[SEVERE] graphql_codegen on lib/data/graphql/schema.graphql:

Bad state: Failed to generate type for UserProfileInput.
```

### 正しい例

正しい方法は、`input`の中でも`input`タイプを使用することです。

```graphql
input CreateUserInput {
  name: String!
  email: String!
  profile: UserProfileInput # これが正しい
}

input UserProfileInput {
  bio: String!
  avatarUrl: String!
}
```
これらの4つの概念を押さえておけば、GraphQLの基本は大丈夫です！

## **graphql_codegenで自動生成する方法**

https://pub.dev/packages/graphql_codegen

### ステップ1: パッケージのインストール

まず、必要なパッケージをインストールします。

```yaml
dependencies:
  flutter:
    sdk: flutter
  graphql_flutter: # GraphQLクライアント用のパッケージ

dev_dependencies:
  build_runner: # コード生成のためのパッケージ
  graphql_codegen: # GraphQLクエリやミューテーションの自動生成用
```

### ステップ2: ディレクトリとファイルの作成

次に、`lib/data/graphql`ディレクトリを作成し、その中に以下のファイルを追加します。各ファイルの役割について簡単に説明します。

#### 1. クエリファイル

```graphql:lib/data/graphql/queries/get_user_query.graphql
query GetUsers {
  users {
    id
    name
  }
}
```

#### 2. ミューテーションファイル

```graphql:lib/data/graphql/mutations/add_user_mutation.graphql
mutation AddUser($input: CreateUserInput!) {
  addUser(input: $input) {
    id
    name
  }
}
```

#### 3. スキーマファイル

GraphQLのスキーマを定義します。

```graphql:lib/data/graphql/schema.graphql
type Query {
  users: [User!]!
  # !は必須、非nullの場合graphqlでは?は不要
  # []は配列
  # クエリが複数ある場合は、ここに追加して記載する。
}

type Mutation {
  addUser(input: CreateUserInput!): User!
  # ミューテーションが複数ある場合は、ここに追加して記載する。
}

# Userタイプの定義
type User {
  id: ID!
  name: String!
  email: String!
}

# 入力タイプの定義
input CreateUserInput {
  name: String!
  email: String!
  profile: UserProfileInput
}

input UserProfileInput {
  bio: String!
  avatarUrl: String!
}
```

### ステップ3: `build.yaml`の設定

`build.yaml`ファイルに以下の設定を追加して、コード生成を自動化します。これにより、指定したディレクトリ内のGraphQLファイルから自動的にDartコードが生成されます。

```yaml
graphql_codegen:
  options:
    scopes:
      - lib/data/graphql/**
    clients:
      - graphql
      - graphql_flutter
```
:::note alert
/の後に **がないとファイルが認識されないので注意:eyes:
:::


### ステップ4: コード生成コマンドの実行

次に、コード生成コマンドを実行してDartコードを自動生成します。

```sh
flutter pub run build_runner build --delete-conflicting-outputs
```

### 自動生成されたコード

<details><summary>クエリ[GetUsers]</summary>


```dart:lib/data/graphql/queries/get_user_query.graphql.dart
import 'package:gql/ast.dart';

class Query$GetUsers {
  Query$GetUsers({
    required this.users,
    this.$__typename = 'Query',
  });

  factory Query$GetUsers.fromJson(Map<String, dynamic> json) {
    final l$users = json['users'];
    final l$$__typename = json['__typename'];
    return Query$GetUsers(
      users: (l$users as List<dynamic>)
          .map(
              (e) => Query$GetUsers$users.fromJson((e as Map<String, dynamic>)))
          .toList(),
      $__typename: (l$$__typename as String),
    );
  }

  final List<Query$GetUsers$users> users;

  final String $__typename;

  Map<String, dynamic> toJson() {
    final _resultData = <String, dynamic>{};
    final l$users = users;
    _resultData['users'] = l$users.map((e) => e.toJson()).toList();
    final l$$__typename = $__typename;
    _resultData['__typename'] = l$$__typename;
    return _resultData;
  }

  @override
  int get hashCode {
    final l$users = users;
    final l$$__typename = $__typename;
    return Object.hashAll([
      Object.hashAll(l$users.map((v) => v)),
      l$$__typename,
    ]);
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (!(other is Query$GetUsers) || runtimeType != other.runtimeType) {
      return false;
    }
    final l$users = users;
    final lOther$users = other.users;
    if (l$users.length != lOther$users.length) {
      return false;
    }
    for (int i = 0; i < l$users.length; i++) {
      final l$users$entry = l$users[i];
      final lOther$users$entry = lOther$users[i];
      if (l$users$entry != lOther$users$entry) {
        return false;
      }
    }
    final l$$__typename = $__typename;
    final lOther$$__typename = other.$__typename;
    if (l$$__typename != lOther$$__typename) {
      return false;
    }
    return true;
  }
}

extension UtilityExtension$Query$GetUsers on Query$GetUsers {
  CopyWith$Query$GetUsers<Query$GetUsers> get copyWith =>
      CopyWith$Query$GetUsers(
        this,
        (i) => i,
      );
}

abstract class CopyWith$Query$GetUsers<TRes> {
  factory CopyWith$Query$GetUsers(
    Query$GetUsers instance,
    TRes Function(Query$GetUsers) then,
  ) = _CopyWithImpl$Query$GetUsers;

  factory CopyWith$Query$GetUsers.stub(TRes res) =
      _CopyWithStubImpl$Query$GetUsers;

  TRes call({
    List<Query$GetUsers$users>? users,
    String? $__typename,
  });
  TRes users(
      Iterable<Query$GetUsers$users> Function(
              Iterable<CopyWith$Query$GetUsers$users<Query$GetUsers$users>>)
          _fn);
}

class _CopyWithImpl$Query$GetUsers<TRes>
    implements CopyWith$Query$GetUsers<TRes> {
  _CopyWithImpl$Query$GetUsers(
    this._instance,
    this._then,
  );

  final Query$GetUsers _instance;

  final TRes Function(Query$GetUsers) _then;

  static const _undefined = <dynamic, dynamic>{};

  TRes call({
    Object? users = _undefined,
    Object? $__typename = _undefined,
  }) =>
      _then(Query$GetUsers(
        users: users == _undefined || users == null
            ? _instance.users
            : (users as List<Query$GetUsers$users>),
        $__typename: $__typename == _undefined || $__typename == null
            ? _instance.$__typename
            : ($__typename as String),
      ));

  TRes users(
          Iterable<Query$GetUsers$users> Function(
                  Iterable<CopyWith$Query$GetUsers$users<Query$GetUsers$users>>)
              _fn) =>
      call(
          users: _fn(_instance.users.map((e) => CopyWith$Query$GetUsers$users(
                e,
                (i) => i,
              ))).toList());
}

class _CopyWithStubImpl$Query$GetUsers<TRes>
    implements CopyWith$Query$GetUsers<TRes> {
  _CopyWithStubImpl$Query$GetUsers(this._res);

  TRes _res;

  call({
    List<Query$GetUsers$users>? users,
    String? $__typename,
  }) =>
      _res;

  users(_fn) => _res;
}

const documentNodeQueryGetUsers = DocumentNode(definitions: [
  OperationDefinitionNode(
    type: OperationType.query,
    name: NameNode(value: 'GetUsers'),
    variableDefinitions: [],
    directives: [],
    selectionSet: SelectionSetNode(selections: [
      FieldNode(
        name: NameNode(value: 'users'),
        alias: null,
        arguments: [],
        directives: [],
        selectionSet: SelectionSetNode(selections: [
          FieldNode(
            name: NameNode(value: 'id'),
            alias: null,
            arguments: [],
            directives: [],
            selectionSet: null,
          ),
          FieldNode(
            name: NameNode(value: 'name'),
            alias: null,
            arguments: [],
            directives: [],
            selectionSet: null,
          ),
          FieldNode(
            name: NameNode(value: '__typename'),
            alias: null,
            arguments: [],
            directives: [],
            selectionSet: null,
          ),
        ]),
      ),
      FieldNode(
        name: NameNode(value: '__typename'),
        alias: null,
        arguments: [],
        directives: [],
        selectionSet: null,
      ),
    ]),
  ),
]);

class Query$GetUsers$users {
  Query$GetUsers$users({
    required this.id,
    required this.name,
    this.$__typename = 'User',
  });

  factory Query$GetUsers$users.fromJson(Map<String, dynamic> json) {
    final l$id = json['id'];
    final l$name = json['name'];
    final l$$__typename = json['__typename'];
    return Query$GetUsers$users(
      id: (l$id as String),
      name: (l$name as String),
      $__typename: (l$$__typename as String),
    );
  }

  final String id;

  final String name;

  final String $__typename;

  Map<String, dynamic> toJson() {
    final _resultData = <String, dynamic>{};
    final l$id = id;
    _resultData['id'] = l$id;
    final l$name = name;
    _resultData['name'] = l$name;
    final l$$__typename = $__typename;
    _resultData['__typename'] = l$$__typename;
    return _resultData;
  }

  @override
  int get hashCode {
    final l$id = id;
    final l$name = name;
    final l$$__typename = $__typename;
    return Object.hashAll([
      l$id,
      l$name,
      l$$__typename,
    ]);
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (!(other is Query$GetUsers$users) || runtimeType != other.runtimeType) {
      return false;
    }
    final l$id = id;
    final lOther$id = other.id;
    if (l$id != lOther$id) {
      return false;
    }
    final l$name = name;
    final lOther$name = other.name;
    if (l$name != lOther$name) {
      return false;
    }
    final l$$__typename = $__typename;
    final lOther$$__typename = other.$__typename;
    if (l$$__typename != lOther$$__typename) {
      return false;
    }
    return true;
  }
}

extension UtilityExtension$Query$GetUsers$users on Query$GetUsers$users {
  CopyWith$Query$GetUsers$users<Query$GetUsers$users> get copyWith =>
      CopyWith$Query$GetUsers$users(
        this,
        (i) => i,
      );
}

abstract class CopyWith$Query$GetUsers$users<TRes> {
  factory CopyWith$Query$GetUsers$users(
    Query$GetUsers$users instance,
    TRes Function(Query$GetUsers$users) then,
  ) = _CopyWithImpl$Query$GetUsers$users;

  factory CopyWith$Query$GetUsers$users.stub(TRes res) =
      _CopyWithStubImpl$Query$GetUsers$users;

  TRes call({
    String? id,
    String? name,
    String? $__typename,
  });
}

class _CopyWithImpl$Query$GetUsers$users<TRes>
    implements CopyWith$Query$GetUsers$users<TRes> {
  _CopyWithImpl$Query$GetUsers$users(
    this._instance,
    this._then,
  );

  final Query$GetUsers$users _instance;

  final TRes Function(Query$GetUsers$users) _then;

  static const _undefined = <dynamic, dynamic>{};

  TRes call({
    Object? id = _undefined,
    Object? name = _undefined,
    Object? $__typename = _undefined,
  }) =>
      _then(Query$GetUsers$users(
        id: id == _undefined || id == null ? _instance.id : (id as String),
        name: name == _undefined || name == null
            ? _instance.name
            : (name as String),
        $__typename: $__typename == _undefined || $__typename == null
            ? _instance.$__typename
            : ($__typename as String),
      ));
}

class _CopyWithStubImpl$Query$GetUsers$users<TRes>
    implements CopyWith$Query$GetUsers$users<TRes> {
  _CopyWithStubImpl$Query$GetUsers$users(this._res);

  TRes _res;

  call({
    String? id,
    String? name,
    String? $__typename,
  }) =>
      _res;
}

```

</details>

<details><summary>ミューテーション[AddUser]</summary>

```dart:lib/data/graphql/mutations/add_user_mutation.graphql.dart
import 'package:gql/ast.dart';

class Query$GetUsers {
  Query$GetUsers({
    required this.users,
    this.$__typename = 'Query',
  });

  factory Query$GetUsers.fromJson(Map<String, dynamic> json) {
    final l$users = json['users'];
    final l$$__typename = json['__typename'];
    return Query$GetUsers(
      users: (l$users as List<dynamic>)
          .map(
              (e) => Query$GetUsers$users.fromJson((e as Map<String, dynamic>)))
          .toList(),
      $__typename: (l$$__typename as String),
    );
  }

  final List<Query$GetUsers$users> users;

  final String $__typename;

  Map<String, dynamic> toJson() {
    final _resultData = <String, dynamic>{};
    final l$users = users;
    _resultData['users'] = l$users.map((e) => e.toJson()).toList();
    final l$$__typename = $__typename;
    _resultData['__typename'] = l$$__typename;
    return _resultData;
  }

  @override
  int get hashCode {
    final l$users = users;
    final l$$__typename = $__typename;
    return Object.hashAll([
      Object.hashAll(l$users.map((v) => v)),
      l$$__typename,
    ]);
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (!(other is Query$GetUsers) || runtimeType != other.runtimeType) {
      return false;
    }
    final l$users = users;
    final lOther$users = other.users;
    if (l$users.length != lOther$users.length) {
      return false;
    }
    for (int i = 0; i < l$users.length; i++) {
      final l$users$entry = l$users[i];
      final lOther$users$entry = lOther$users[i];
      if (l$users$entry != lOther$users$entry) {
        return false;
      }
    }
    final l$$__typename = $__typename;
    final lOther$$__typename = other.$__typename;
    if (l$$__typename != lOther$$__typename) {
      return false;
    }
    return true;
  }
}

extension UtilityExtension$Query$GetUsers on Query$GetUsers {
  CopyWith$Query$GetUsers<Query$GetUsers> get copyWith =>
      CopyWith$Query$GetUsers(
        this,
        (i) => i,
      );
}

abstract class CopyWith$Query$GetUsers<TRes> {
  factory CopyWith$Query$GetUsers(
    Query$GetUsers instance,
    TRes Function(Query$GetUsers) then,
  ) = _CopyWithImpl$Query$GetUsers;

  factory CopyWith$Query$GetUsers.stub(TRes res) =
      _CopyWithStubImpl$Query$GetUsers;

  TRes call({
    List<Query$GetUsers$users>? users,
    String? $__typename,
  });
  TRes users(
      Iterable<Query$GetUsers$users> Function(
              Iterable<CopyWith$Query$GetUsers$users<Query$GetUsers$users>>)
          _fn);
}

class _CopyWithImpl$Query$GetUsers<TRes>
    implements CopyWith$Query$GetUsers<TRes> {
  _CopyWithImpl$Query$GetUsers(
    this._instance,
    this._then,
  );

  final Query$GetUsers _instance;

  final TRes Function(Query$GetUsers) _then;

  static const _undefined = <dynamic, dynamic>{};

  TRes call({
    Object? users = _undefined,
    Object? $__typename = _undefined,
  }) =>
      _then(Query$GetUsers(
        users: users == _undefined || users == null
            ? _instance.users
            : (users as List<Query$GetUsers$users>),
        $__typename: $__typename == _undefined || $__typename == null
            ? _instance.$__typename
            : ($__typename as String),
      ));

  TRes users(
          Iterable<Query$GetUsers$users> Function(
                  Iterable<CopyWith$Query$GetUsers$users<Query$GetUsers$users>>)
              _fn) =>
      call(
          users: _fn(_instance.users.map((e) => CopyWith$Query$GetUsers$users(
                e,
                (i) => i,
              ))).toList());
}

class _CopyWithStubImpl$Query$GetUsers<TRes>
    implements CopyWith$Query$GetUsers<TRes> {
  _CopyWithStubImpl$Query$GetUsers(this._res);

  TRes _res;

  call({
    List<Query$GetUsers$users>? users,
    String? $__typename,
  }) =>
      _res;

  users(_fn) => _res;
}

const documentNodeQueryGetUsers = DocumentNode(definitions: [
  OperationDefinitionNode(
    type: OperationType.query,
    name: NameNode(value: 'GetUsers'),
    variableDefinitions: [],
    directives: [],
    selectionSet: SelectionSetNode(selections: [
      FieldNode(
        name: NameNode(value: 'users'),
        alias: null,
        arguments: [],
        directives: [],
        selectionSet: SelectionSetNode(selections: [
          FieldNode(
            name: NameNode(value: 'id'),
            alias: null,
            arguments: [],
            directives: [],
            selectionSet: null,
          ),
          FieldNode(
            name: NameNode(value: 'name'),
            alias: null,
            arguments: [],
            directives: [],
            selectionSet: null,
          ),
          FieldNode(
            name: NameNode(value: '__typename'),
            alias: null,
            arguments: [],
            directives: [],
            selectionSet: null,
          ),
        ]),
      ),
      FieldNode(
        name: NameNode(value: '__typename'),
        alias: null,
        arguments: [],
        directives: [],
        selectionSet: null,
      ),
    ]),
  ),
]);

class Query$GetUsers$users {
  Query$GetUsers$users({
    required this.id,
    required this.name,
    this.$__typename = 'User',
  });

  factory Query$GetUsers$users.fromJson(Map<String, dynamic> json) {
    final l$id = json['id'];
    final l$name = json['name'];
    final l$$__typename = json['__typename'];
    return Query$GetUsers$users(
      id: (l$id as String),
      name: (l$name as String),
      $__typename: (l$$__typename as String),
    );
  }

  final String id;

  final String name;

  final String $__typename;

  Map<String, dynamic> toJson() {
    final _resultData = <String, dynamic>{};
    final l$id = id;
    _resultData['id'] = l$id;
    final l$name = name;
    _resultData['name'] = l$name;
    final l$$__typename = $__typename;
    _resultData['__typename'] = l$$__typename;
    return _resultData;
  }

  @override
  int get hashCode {
    final l$id = id;
    final l$name = name;
    final l$$__typename = $__typename;
    return Object.hashAll([
      l$id,
      l$name,
      l$$__typename,
    ]);
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (!(other is Query$GetUsers$users) || runtimeType != other.runtimeType) {
      return false;
    }
    final l$id = id;
    final lOther$id = other.id;
    if (l$id != lOther$id) {
      return false;
    }
    final l$name = name;
    final lOther$name = other.name;
    if (l$name != lOther$name) {
      return false;
    }
    final l$$__typename = $__typename;
    final lOther$$__typename = other.$__typename;
    if (l$$__typename != lOther$$__typename) {
      return false;
    }
    return true;
  }
}

extension UtilityExtension$Query$GetUsers$users on Query$GetUsers$users {
  CopyWith$Query$GetUsers$users<Query$GetUsers$users> get copyWith =>
      CopyWith$Query$GetUsers$users(
        this,
        (i) => i,
      );
}

abstract class CopyWith$Query$GetUsers$users<TRes> {
  factory CopyWith$Query$GetUsers$users(
    Query$GetUsers$users instance,
    TRes Function(Query$GetUsers$users) then,
  ) = _CopyWithImpl$Query$GetUsers$users;

  factory CopyWith$Query$GetUsers$users.stub(TRes res) =
      _CopyWithStubImpl$Query$GetUsers$users;

  TRes call({
    String? id,
    String? name,
    String? $__typename,
  });
}

class _CopyWithImpl$Query$GetUsers$users<TRes>
    implements CopyWith$Query$GetUsers$users<TRes> {
  _CopyWithImpl$Query$GetUsers$users(
    this._instance,
    this._then,
  );

  final Query$GetUsers$users _instance;

  final TRes Function(Query$GetUsers$users) _then;

  static const _undefined = <dynamic, dynamic>{};

  TRes call({
    Object? id = _undefined,
    Object? name = _undefined,
    Object? $__typename = _undefined,
  }) =>
      _then(Query$GetUsers$users(
        id: id == _undefined || id == null ? _instance.id : (id as String),
        name: name == _undefined || name == null
            ? _instance.name
            : (name as String),
        $__typename: $__typename == _undefined || $__typename == null
            ? _instance.$__typename
            : ($__typename as String),
      ));
}

class _CopyWithStubImpl$Query$GetUsers$users<TRes>
    implements CopyWith$Query$GetUsers$users<TRes> {
  _CopyWithStubImpl$Query$GetUsers$users(this._res);

  TRes _res;

  call({
    String? id,
    String? name,
    String? $__typename,
  }) =>
      _res;
}

```
</details>

### 解説

<dl>
  <dt>クエリ[GetUsers]</dt>
  <dd>サーバーからUserオブジェクトの配列を取得するリクエストを送信します。レスポンスには、idとnameフィールドを持つユーザーの配列が含まれています。</dd>
  <dt>ミューテーション[AddUser]</dt>
  <dd>新しいユーザーを作成するための入力データを受け取ります。レスポンスには、作成されたユーザーのidとnameが含まれています。</dd>
  <dt>スキーマファイル</dt>
  <dd>Userタイプを定義し、CreateUserInputという入力タイプを定義しています。この入力タイプは、新しいユーザーを作成するためのフィールドを持ち、ミューテーションの引数として使用されます。</dd>
</dl>

これにより、GraphQLクエリとミューテーションのレスポンスがどのような形式になるかが明確になります。

### graphql_codegenを使うメリット

`graphql_codegen`を使うことで、以下のようなメリットがあります。

#### 1. 手動コーディングの削減

クエリやミューテーションを手動で書く手間を大幅に減らせます。自動生成されたコードを使用することで、構文エラーを防ぎ、効率的に開発を進めることができます。

#### 2. 型安全性の向上

自動生成されたコードには型が明示的に定義されているため、手書きの場合は実際にAPIを叩いてみないとエラーが検出できなかったのが、コンパイル時にエラーを検出できます。これにより、ランタイムエラーを減らし、バグの少ないコードを作成することができます。

#### 3. メンテナンスの容易さ

GraphQLスキーマが変更された場合でも、自動生成されたコードを再生成するだけで対応できます。これにより、コードのメンテナンスが容易になり、最新のスキーマに追従することができます。

これらのメリットを享受することで、効率的かつ高品質なGraphQLアプリケーションを開発することができます。

## **実際に使ってみた感想**

自分はInput型Type型を理解していない状態で書いてしまい、自動生成が上手くできず大苦戦しました(笑)。
もし自分と同じく苦戦している方がいましたら、もう一度型の定義を見直してみてください
また今後もGraphQLについて新しい発見があれば、記事も更新していこうと思います。
最後まで読んでいただきありがとうございました🙇‍♂️

# 告知
最後にお知らせとなりますが、イーディーエーでは一緒に働くエンジニアを
募集しております。詳しくは採用情報ページをご確認ください。

https://eda-inc.jp/recruit/

みなさまからのご応募をお待ちしております。