LaravelとGraphQLでAPI開発

# :x:追記: ご注意:x:

[folkloreinc/laravel-graphql](https://github.com/folkloreinc/laravel-graphql) こちらの記事で採用しているライブラリが 2019/2/10 にアーカイブされてしまいました😢😢😢
今後ライブラリ導入を検討される方は [nuwave/lighthouse](https://github.com/nuwave/lighthouse) を推奨します。

=========================================================

会社でGraphQLを導入することが決まったものの、知見がないので調べつつ進めています。
GraphQLの情報がそもそも少ない上に、Laravelの情報が少なくソースコードとずっと睨めっこしてました。
自分の情報整理を兼ねて記事にしたいと思います。
間違い等あればご指摘して欲しいです。
GraphQL初めてなので要所要所で感動しました。

※全部まとめて書こうと思ってたんですが、あまりにも長くなりそうなので
今回はType(GraphQL独自の型定義)とQueryとMutationについて記述します。

## シリーズ記事

- LaravelとGraphQLでAPI開発
- [Laravel 5.7 + GraphQL（Install編）](https://qiita.com/ucan-lab/items/32969d6a9b5aa99b475e)
- [Laravel 5.7 + GraphQL（ScalarType編）](https://qiita.com/ucan-lab/items/32ee8dd18974d6146456)
- [Laravel 5.7 + GraphQL（Relation編）](https://qiita.com/ucan-lab/items/4eae9e538be9a7a7c5e9)
- [Laravel 5.7 + GraphQL（Dump Server編）](https://qiita.com/ucan-lab/items/ce9abab3fddec0d2d0eb)
- [Laravel 5.7 + GraphQL（Pagination編）](https://qiita.com/ucan-lab/items/c037d4bd07840af32b94)
- [Laravel 5.7 + GraphQL（ErrorHandling編）](https://qiita.com/ucan-lab/items/fa3bf9b1e7fca1b19d0d)
- [Laravel 5.7 + GraphQL（Test編）](https://qiita.com/ucan-lab/items/1815071cd1cf32d53008)

## 目次

- サンプルリポジトリの紹介
- GraphQL導入メリット
- Laravel5.7インストール
- GraphQLインストール
  - Laravel5.7注意点
  - 追加されるGraphQLコマンド
  - 追加されるGraphQLルーティング
  - GraphQLブラウザ実行環境
- テストデータ作成
- GraphQL Type (ユーザー型定義)
- GraphQL Query (ユーザー取得)
  - ユーザー、ユーザーの一覧の取得例
- GraphQL Mutation (ユーザー登録)
  - ユーザー登録例
  - バリデーション例
- 所感

## サンプルリポジトリ

この記事で使用しているソースコードはこちらのリポジトリにです。
https://github.com/ucan-lab/practice-laravel-graphql

参考用にコミットのリンクも残していこうかと思います。

## GraphQL導入メリット

個人的に感じたメリットをまとめました。

- エンドポイント(ルーティング定義)を気にしなくていい
- Laravelバリデーションが使える
  - これが超絶便利
- dump-serverでデバッグ効率アップ
  - Laravel5.7から追加された新機能
  - GraphQLの実行結果を確認しつつ、dumpの実行結果を確認できる
  - dump-serverを起動していればdumpをコメントしなくて良い
- resolveを使えば融通が利く
- クライアント側は欲しいパラメータだけ指定すれば良い(リソースの節約になる)

## Laravel5.7インストール

[Laravel 5.7 + GraphQL（Install編）](https://qiita.com/ucan-lab/items/32969d6a9b5aa99b475e)

別記事に書きました。

### GraphQLブラウザ実行環境

```shell-session
$ php artisan serve
$ php artisan dump-server
```

http://127.0.0.1:8000/graphiql

GraphQL実行環境でブラウザ上からQueryやMutationのお試しができます。
入力予測や入力履歴が見れるので結構使いやすいです。

#### ショートカットキー

- control + shift + p クエリの整形
- control + Enter クエリの実行
- control + space 自動補完
  - IMEの切り替えショートカットとバッティングした

## テストデータ作成

https://github.com/ucan-lab/practice-laravel-graphql/commit/c5b881b44e39fa3f16ea719c7c38cb9611d94b3c

簡単なシーディングを作ります。

```
$ php artisan make:seeder UsersTableSeeder
$ php artisan migrate:fresh --seed
```

## GraphQL Type (ユーザー型定義)

- https://github.com/ucan-lab/practice-laravel-graphql/commit/6ab1ad983c58cc904c5c0b6f3c8437b7221f1c23
- https://github.com/ucan-lab/practice-laravel-graphql/commit/9e8baf6d31470b42a6e13dc7f8c82fcf21841f1b

query はデータの読み込み、 mutation はデータの書き込みをするときに使います。
とその前にGraphQL独自の型を定義する必要があるのでユーザー型を作ります。

```
$ php artisan make:graphql:type UserType
```

- `app/GraphQL/Type/UserType.php` ファイルが作成されます。
- `config/graphql.php` に型を登録します。

```php
    'types' => [
        App\GraphQL\Type\UserType::class,
    ],
```

### app/GraphQL/Type/UserType.php

```php
<?php

declare(strict_types = 1);

namespace App\GraphQL\Type;

use GraphQL\Type\Definition\Type;
use Folklore\GraphQL\Support\Type as BaseType;
use GraphQL;

/**
 * User型の定義
 */
class UserType extends BaseType
{
    /**
     * 型名の定義と概要
     *
     * @var array
     */
    protected $attributes = [
        'name' => 'UserType',
        'description' => 'A type'
    ];

    /**
     * フィールドに持たせる型と挙動を定義
     *
     * @return array
     */
    public function fields() : array
    {
        return [
            'id' => [
                'type' => Type::id(),
                'description' => 'The id of the user'
            ],
            'name' => [
                'type' => Type::string(),
                'description' => 'The name of user',
            ],
            'email' => [
                'type' => Type::string(),
                'description' => 'The email of user',
            ],
        ];
    }
}
```

`fields` メソッドに許可するカラムを指定します。
`password` など外に出したくない項目は記述しなければokです。

各カラムのスカラー型を指定します。

- `Type::id()` => intとの区別がわからないけど、idっぽいやつ
- `Type::string()`
- `Type::boolean()`
- `Type::int()`
- `Type::float()`
- `Type::listOf()` => 配列
- `Type::nonNull()` => NOT NULL

ここで私は思った、日付型はどうするんだ...と。(別記事に書きます)

## GraphQL Query (ユーザー取得)

- https://github.com/ucan-lab/practice-laravel-graphql/commit/e1f21f724043c6217527470e9886a70220dd0acb
  - https://github.com/ucan-lab/practice-laravel-graphql/commit/7a9abd13fd5c0777dd5ef19ba34735a43a56012d
- https://github.com/ucan-lab/practice-laravel-graphql/commit/d79f2fcfb7029763d408066dec6ac4d89e55bc61
  - https://github.com/ucan-lab/practice-laravel-graphql/commit/0d43798739c1cc8a571085c76e72567d15ce366e

データの読み込みのためQueryを定義します。

```shell-session
$ php artisan make:graphql:query UserQuery
$ php artisan make:graphql:query UsersQuery
```

- `app/GraphQL/Query/UserQuery.php`
- `app/GraphQL/Query/UsersQuery.php`

単体とリストで取得したい場合があるので2種類のユーザークエリーを作成します。

`config/graphql.php` 型定義の時と同様にconfigにクエリーを登録します。

```php
    'schemas' => [
        'default' => [
            'query' => [
                App\GraphQL\Query\UserQuery::class,
                App\GraphQL\Query\UsersQuery::class,
            ],
            'mutation' => [

            ]
        ]
    ],
```

### app/GraphQL/Query/UserQuery.php

```php
<?php

declare(strict_types = 1);

namespace App\GraphQL\Query;

use Folklore\GraphQL\Support\Query;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ObjectType;
use GraphQL;
use App\User;

/**
 * Userクエリの定義
 */
class UserQuery extends Query
{
    /**
     * クエリ名の定義と概要
     *
     * @var array
     */
    protected $attributes = [
        'name' => 'user',
        'description' => 'user query'
    ];

    /**
     * クエリが扱う型を定義
     *
     * @return ObjectType
     */
    public function type() : ObjectType
    {
        return GraphQL::type('UserType');
    }

    /**
     * クエリが取り得る引数を定義
     *
     * @return array
     */
    public function args() : array
    {
        return [
            'id' => [
                'name' => 'id',
                'type' => Type::id(),
            ],
        ];
    }

    /**
     * クエリに対する実処理
     *
     * @param array $root
     * @param array $args
     * @return User
     */
    public function resolve(array $root, array $args) : User
    {
        $query = User::query();

        if (isset($args['id'])) {
            $query->where('id', $args['id']);
        }

        return $query->first();
    }
}
```

- `$attributes` プロパティの `name` キーに設定した名前がクエリ名として扱われます。
- `type()` クエリが返すGraphQLの型を指定します。
- `args()` クエリが受け取れるパラメータを定義します。(検索条件等)
- `resolve()` コントローラの代わりにここで色々やります。
  - `$args` にパラメータが入ってくるのでそれに応じて実装しよう
  - 処理が複雑になるとここが肥大化したり重複コードが量産されるので設計大事です

### GraphQL Query ブラウザ実行環境でテスト

左側にクエリを記述する。

```graphql
query {
  user {
    id
    name
    email
  }
}
```

右側に結果が返ってくる。

```json
{
  "data": {
    "user": {
      "id": "1",
      "name": "Maximus Bechtelar",
      "email": "tshields@example.net"
    }
  }
}
```

- フィールドを区切るカンマは合ってもなくてもいいらしい。
- `{user{id name email}}` 極端な話、クエリはこれで通る。(今回は見やすさ重視でいきます)
  - queryも省略できる
- `{クエリ名{フィールド名}}` で簡単に取得できる🍏

これは...神なのでは！！！
クエリクラス作って、configに登録したらできてしまいます！！
サーバー側がめっちゃ楽できるし、クライアント側もシンプルにデータを取ってこれる！

```graphql
query {
  user(id: 10) {
    id
    name
    email
  }
}
```

```json
{
  "data": {
    "user": {
      "id": "10",
      "name": "Elyssa Marvin",
      "email": "bryana.hyatt@example.com"
    }
  }
}
```

ちなみに `user(id: 10)` と引数を渡してあげればid:10のユーザーを取ってこれます。
指定しなかったときは、1つ目のデータが取れた訳ですが、そもそもidの指定がなかったらエラーにしたいな。

この時、私は思った。
これフォームリクエストでバリデーション書いていくのしんどいのでは？(ミューテーションで一緒に解説します)

### app/GraphQL/Query/UsersQuery.php

UserQueryはユーザー詳細ページを表示するときには使えますが、
ユーザー一覧を表示するときは複数ユーザー取得したいので、複数ユーザーを取ってくる例も記載します。

```php
<?php

namespace App\GraphQL\Query;

use Folklore\GraphQL\Support\Query;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ListOfType;
use GraphQL;
use Illuminate\Database\Eloquent\Collection;
use App\User;

/**
 * Usersクエリの定義
 */
class UsersQuery extends Query
{
    /**
     * クエリ名の定義と概要
     *
     * @var array
     */
    protected $attributes = [
        'name' => 'users',
        'description' => 'users query'
    ];

    /**
     * クエリが扱う型を定義
     *
     * @return ObjectType
     */
    public function type() : ListOfType
    {
        return Type::listOf(GraphQL::type('UserType'));
    }

    /**
     * クエリが取り得る引数を定義
     *
     * @return array
     */
    public function args() : array
    {
        return [
            'id' => [
                'name' => 'id',
                'type' => Type::id(),
            ],
            'ids' => [
                'name' => 'ids',
                'type' => Type::listOf(Type::id()),
            ],
            'email' => [
                'name' => 'email',
                'type' => Type::string(),
            ]
        ];
    }

    /**
     * クエリに対する実処理
     *
     * @param array $root
     * @param array $args
     * @return Collection
     */
    public function resolve(array $root, array $args) : Collection
    {
        $query = User::query();

        if (isset($args['id'])) {
            $query->where('id', $args['id']);
        }

        if (isset($args['ids'])) {
            $query->whereIn('id', $args['ids']);
        }

        if (isset($args['email'])) {
            $query->where('email', $args['email']);
        }

        return $query->get();
    }
}
```

- `type()` で ListOfType を返す
- `resolve` で Collection を返す

ポイントはこの2点です。
UsersQueryでは引数にidsで配列を受け取れるようにしています。

```graphql

query {
  users(ids: [10, 15, 20]) {
    id
    name
    email
  }
}
```

```json
{
  "data": {
    "users": [
      {
        "id": "10",
        "name": "Elyssa Marvin",
        "email": "bryana.hyatt@example.com"
      },
      {
        "id": "15",
        "name": "Mr. Jeromy Pacocha IV",
        "email": "fredy13@example.net"
      },
      {
        "id": "20",
        "name": "Prof. Janelle Raynor IV",
        "email": "gprice@example.org"
      }
    ]
  }
}
```

ユーザーの一覧を取ってこれました。
いい感じにやれそうです。

この時、私は思った。ページネーションどうすればいいんだ。(これから検討します)

## GraphQL Mutation (ユーザー登録)

- https://github.com/ucan-lab/practice-laravel-graphql/commit/64ab86bda1a82f621bf4a2a965e0e7ab8cfd8448
- https://github.com/ucan-lab/practice-laravel-graphql/commit/88bd1fadf1480b33bca2c0df78afd07dcbbfdf0d

データの書き込むのためMutationを定義します。

```shell-session
$ php artisan make:graphql:mutation CreateUserMutation
```

- `app/GraphQL/Mutation/CreateUserMutation.php`

単体とリストで取得したい場合があるので2種類のユーザークエリーを作成します。
命名規則で悩むがシンプルにAction単位で作った方が良さそう。

`config/graphql.php` 型定義の時と同様にconfigにミューテーションを登録します。

```php
    'schemas' => [
        'default' => [
            'query' => [
                App\GraphQL\Query\UserQuery::class,
                App\GraphQL\Query\UsersQuery::class,
            ],
            'mutation' => [
                App\GraphQL\Mutation\CreateUserMutation::class,
            ]
        ]
    ],
```

### app/GraphQL/Mutation/CreateUserMutation.php

```php
<?php

declare(strict_types = 1);

namespace App\GraphQL\Mutation;

use Folklore\GraphQL\Support\Mutation;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ObjectType;
use GraphQL;
use Illuminate\Support\Facades\Hash;
use App\User;

/**
 * ユーザーを登録するミューテーション
 */
class CreateUserMutation extends Mutation
{
    /**
     * ミューテーション名の定義と概要
     *
     * @var array
     */
    protected $attributes = [
        'name' => 'CreateUser',
        'description' => 'CreateUser mutation'
    ];

    /**
     * ミューテーションが扱う型を定義
     *
     * @return ObjectType
     */
    public function type() : ObjectType
    {
        return GraphQL::type('UserType');
    }

    /**
     * ミューテーションが取り得る引数を定義
     *
     * @return array
     */
    public function args() : array
    {
        return [
            'name' => [
                'name' => 'name',
                'type' => Type::string(),
                'rules' => ['required', 'string', 'max:255'],
            ],
            'email' => [
                'name' => 'email',
                'type' => Type::string(),
                'rules' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            ],
            'password' => [
                'name' => 'password',
                'type' => Type::string(),
                'rules' => ['required', 'string', 'min:6'],
            ],
        ];
    }

    /**
     * ミューテーションに対する実処理
     *
     * @param array $root
     * @param array $args
     * @return User
     */
    public function resolve($root, $args, $context, ResolveInfo $info) : User
    {
        $user = User::create([
            'name' => $args['name'],
            'email' => $args['email'],
            'password' => Hash::make($args['password']),
        ]);

        return $user;
    }
}
```

ユーザーを登録するミューテーションを書きました。とてもシンプルに書けます。
`rules` でLaravelのバリデーションがそのまま使えるのが凄くて、フォームリクエストを作ったりしなくていいです。
resolveに来る前にバリデーションチェックしてくれます。これは凄いなと思う。

### GraphQL Mutation (ユーザー登録) ブラウザ実行環境でテスト

```graphql
mutation {
  CreateUser (
    name: "test"
    email: "sample@example.com"
    password: "secret"
  ) {
    id
    name
    email
  }
}
```

```json
{
  "data": {
    "CreateUser": {
      "id": "31",
      "name": "test",
      "email": "sample@example.com"
    }
  }
}
```

ミューテーションでUserTypeを返しているので、登録したユーザー情報も取得できます。
クエリーで使ったUserTypeを使えるのでめっちゃ便利です。

#### バリデーションエラー時

```graphql
mutation {
  CreateUser (
    email: "sample@example.com"
    password: "secret"
  ) {
    id
    name
    email
  }
}
```

必須項目のnameを削除して実行します。

```json
{
  "data": {
    "CreateUser": null
  },
  "errors": [
    {
      "message": "validation",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "validation": {
        "name": [
          "The name field is required."
        ],
        "email": [
          "The email has already been taken."
        ]
      }
    }
  ]
}
```

こんな感じでバリデーションエラーが返ってきます。
emailも既に登録済みのデータなのでエラーになってますね！

## 所感

GraphQLめっちゃ凄い。
ただLaravel+GraphQLの情報が少なく、間違った(変更された?)情報も多くて結構ハマりました。
私の方法もベストプラクティスなのか分からないので詳しい方のご意見を聞きたいです。

- 日付型(スカラー型)の追加
- リレーションデータの取得

ここまで書くまでに長くなったので、こちらは別記事で紹介したいと思います。

- Lighthouse がいいらしい
  - https://github.com/nuwave/lighthouse

ここまで書いてきてアレなんですが、Lighthouseを使った方が良いらしいです。
GraphQL自体がまず分かってなかったのでまずこちらを試してみました。
Lighthouseを使うと記述量が圧倒的に減るのと、スキーマ出力できるのが超絶メリットだと思いました。
これは開発効率がさらに上がりそうです。

## 参考情報

- https://github.com/Folkloreatelier/laravel-graphql
- https://graphql.org/learn
- https://qiita.com/nunulk/items/5c506d8ef391f5e9971c
- https://qiita.com/acro5piano/items/b9e3acd4af5ec14d7bb1