Laravel+GraphQL Lighthouse チュートリアル 前半

## チュートリアル

https://lighthouse-php.com/tutorial

これを進めます。
英語のためGoogle翻訳して進めます。
(わかりやすい和訳できる方の編集リクエスト歓迎です🙇‍♂️)

## 環境

- PHP 7.4.5
- Composer 1.10.5
- Laravel 7.10.3
- Lighthouse 4.12.4
- MySQL 8.0.19

## 使用するパッケージ

https://github.com/nuwave/lighthouse

## 完成版のソースコード

https://github.com/nuwave/lighthouse-tutorial

## What is GraphQL?

GraphQLは、APIのクエリ言語であり、既存のデータでこれらのクエリを実行するためのランタイムです。

GraphQLは、API内のデータの完全で理解可能な説明を提供し、クライアントに必要なものだけを正確に要求する力を与え、APIを長期間にわたって容易に進化させ、強力な開発者ツールを有効にします。

![playground.0f01f1e7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/f1445947-64cf-a657-1ec6-03e884b85edf.png)

GraphQLは仕様としてのみリリースされています。
http://spec.graphql.org

GraphQLには、APIのスキーマを定義するために使用される独自の型システムがあります。
スキーマを記述するための構文は、スキーマ定義言語または短いSDLと呼ばれます。
https://www.prisma.io/blog/graphql-sdl-schema-definition-language-6755bcb9ce51

これは、SDLを使用してUserという型と、別の型のPostとの関係を定義する方法の例です。

```graphql
type User {
  id: ID!
  name: String!
  posts: [Post!]!
}

type Post {
  title: String!
  author: User!
}
```

`User`と`Post`の間に1対多の関係を作成しています。
`User`型には、`Post`型のリストを返す`posts`フィールドがあります。

また、`author`フィールドを介して`Post`から`User`への逆関係を定義しました。

## What is Lighthouse?

LighthouseはLaravelプロジェクトと統合して、独自のGraphQLサーバーへのサービス提供を容易にします。
LighthouseでGraphQLサーバーを構築するプロセスは、3つのステップで説明できます。

1. GraphQLスキーマ定義言語を使用してデータの形状を定義する
2. ディレクティブを使用してスキーマに命を吹き込む
3. 必要な場所にカスタム機能を追加する

![flow.f9dcf86d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/054d06cd-dd84-8b2d-3b02-95f60a4a28aa.png)
(アプリケーション中のGraphQLの役割)

## Agenda

このチュートリアルでは、簡単なブログ用のGraphQL APIをゼロから作成します。

1. Laravel
2. Lighthouse
3. GraphQL Playground

所要時間: 15分程度

## Installation

### Laravel プロジェクトの作成

```shell-session
$ laravel new lighthouse-tutorial
```

### マイグレーション

```shell-session
$ php artisan migrate
```

### テストデータの作成

```shell-session
$ php artisan tinker
>>> factory('App\User', 10)->create();
```

### Lighthouse ライブラリの導入

https://github.com/nuwave/lighthouse

```shell-session
$ composer require nuwave/lighthouse
```

### GraphQL 実行環境ライブラリの導入

https://github.com/mll-lab/laravel-graphql-playground

```shell-session
$ composer require mll-lab/laravel-graphql-playground
```

### デフォルトスキーマの公開

```shell-session
$ php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=schema
```

### Laravelサーバーの起動し、GraphQLの実行

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

http://127.0.0.1:8000/graphql-playground

```graphql
{
  user(id: 1) {
    id
    name
    email
  }
}
```

## The Models

この最初のパートでは、モデルとデータベースの移行を設定する方法を示します。
GraphQLまたはLighthouseに関連する詳細は含まれていません。

私たちのブログはいくつかの単純なルールに従っています

- ユーザーは複数の投稿を公開できます
- 各投稿には、匿名ユーザーからの複数のコメントを含めることができます

これをデータベーススキーマで次のようにモデル化できます。

![model.08d79f32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/ff47c0e3-7114-2c64-75e3-8f9303900273.png)

まず、投稿とコメントのモデルと移行を定義します。

```shell-session
$ php artisan make:model -m Post
```

```php:app/Post.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Post extends Model
{
    public function author(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }
}
```

```php:database/migrations/2020_05_07_082047_create_posts_table.php
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePostsTable extends Migration
{
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id('id');
            $table->unsignedBigInteger('author_id');
            $table->string('title');
            $table->string('content');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
}
```

```shell-session
$ php artisan make:model -m Comment
```

```php:app/Comment.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Comment extends Model
{
    public function post(): BelongsTo
    {
        return $this->belongsTo(Post::class);
    }
}
```

```php:database/migrations/2020_05_07_082642_create_comments_table.php
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateCommentsTable extends Migration
{
    public function up(): void
    {
        Schema::create('comments', function (Blueprint $table) {
            $table->id('id');
            $table->unsignedBigInteger('post_id');
            $table->string('reply');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('comments');
    }
}
```

```shell-session
$ php artisan migrate
```

`app/User.php` を編集します。

```php:app/User.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Relations\HasMany;  // 追加
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    // 追加
    public function posts(): HasMany
    {
        return $this->hasMany(Post::class, 'author_id');
    }
}
```

## The Schema

`graphql/schema.graphql`を編集して、作成したEloquentモデルに基づいてブログスキーマを定義しましょう。

ルートクエリタイプに投稿を取得するための2つのクエリを追加します。

```graphql:graphql/schema.graphql
type Query {
    # 追加
    posts: [Post!]! @all
    post(id: Int! @eq): Post @find
    # .. 省略
}
```

- 先頭文字が `#` で始まる場合と `"` で囲まれてる文字はコメントとして扱われます。
  - `"コメント"`, `# コメント`

Lighthouseがクエリを解決する方法を知る方法は、
コンベンションベースの命名（タイプ名Postはモデルの名前でもある）とサーバー側ディレクティブの使用の組み合わせです。

`@all`はすべてのPostモデルのリストを返します
`@find`と`@eq`を組み合わせて、IDで単一の投稿を取得します

データの形状を明確に定義するタイプ定義を追加します。

```graphql:graphql/schema.graphql
type User {
  id: ID!
  name: String!
  email: String!
  created_at: DateTime!
  updated_at: DateTime!
  posts: [Post!]! @hasMany
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User! @belongsTo
  comments: [Comment!]! @hasMany
}

type Comment {
  id: ID!
  reply: String!
  post: Post! @belongsTo
}
```

Eloquentと同様に、`@belongsTo`ディレクティブと`@hasMany`ディレクティブを使用してタイプ間の関係を表現します。


## テストデータ作成

```shell-session
$ php artisan make:factory PostFactory -m Post
$ php artisan make:factory CommentFactory -m Comment
$ php artisan make:seed UserSeeder
$ php artisan make:seed PostSeeder
$ php artisan make:seed CommentSeeder
```

`UserFactory` は元々用意してくれています。

```php:database/factories/PostFactory.php
<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Post;
use Faker\Generator as Faker;

$factory->define(Post::class, function (Faker $faker) {
    return [
        'title' => $faker->sentence(),
        'content' => $faker->text(),
    ];
});
```

```php:database/factories/CommentFactory.php
<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Comment;
use Faker\Generator as Faker;

$factory->define(Comment::class, function (Faker $faker) {
    return [
        'reply' => $faker->sentence(),
    ];
});
```

```php:database/seeds/UserSeeder.php
<?php

use Illuminate\Database\Seeder;
use App\User;

class UserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        User::truncate();
        factory(User::class, 3)->create();
    }
}
```

```php:database/seeds/PostSeeder.php
<?php

use App\Post;
use App\User;
use Illuminate\Database\Seeder;

class PostSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Post::truncate();
        foreach (User::all() as $user) {
            factory(Post::class, 4)->create(['author_id' => $user->id]);
        }
    }
}
```

```php:database/seeds/CommentSeeder.php
<?php

use App\Comment;
use App\Post;
use Illuminate\Database\Seeder;

class CommentSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Comment::truncate();
        foreach (Post::all() as $post) {
            factory(Comment::class, 5)->create(['post_id' => $post->id]);
        }
    }
}
```

```php:database/seeds/DatabaseSeeder.php
<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call(UserSeeder::class);
        $this->call(PostSeeder::class);
        $this->call(CommentSeeder::class);
    }
}
```

```shell-session
$ php artisan db:seed
```

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

## The Result

http://127.0.0.1:8000/graphql-playground

```graphql
{
  posts {
    id
    title
    author {
      name
    }
    comments {
      id
      reply
    }
  }
}
```

<img width="1672" alt="スクリーンショット 2020-05-07 22.58.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/514cbafb-73c9-3a83-70ff-6d9d0d303a6d.png">

データベース内のすべての投稿のリストと、そのすべてのコメントおよび作成者の名前を取得できます。
今回はGraphQLの機能と、Lighthouseを使用してLaravelで独自のサーバーを簡単に構築する方法を学びました。

## To be continued ...

- ページネーション
- Eloquentモデルの作成と更新
- バリデーション

追記: 後半の記事を書きました。

- [Laravel+GraphQL Lighthouse チュートリアル 後半](https://qiita.com/ucan-lab/items/bc9222c79af45b4ec252)