Laravel + Vue + GraphQLでデータ取得

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、前回の「axios不要!fetch()でAjax通信する方法」では、私が最近まで知らなかった新しい技術にチャレンジするというコンセプトでお届けしました。

そして、今回もその流れで前からやってみたかった内容をお届けしたいと思います。

それは・・・・・・

GraphQL

です。

GraphQLとは、簡単にいうと今までのSQL文とは違った形式でDB操作をする技術です。

例えば、これまでのSQL文は次のようなものが一般的でした。

SELECT id, name FROM users

これが、GraphQLでは、次のようなデータを送信することになります。

{
    users {
      id
      name
    }
}

(おそらくグラフィカルなんでこの名前がついたのかな、と勝手に思っています😊)

また、複数テーブルからデータ取得ができるので、Rest APIでは1個ずつ処理していたものが一気に完了します。

{
    users { // 👈 usersデータ
      id
      name
    }
    posts { // 👈 postsデータ
      id
      title
    }
}

なお、DB操作したいテーブルだけをschemaと呼ばれるファイルで設定した場合だけ有効になるので、セキュリティ的にも安心です😊✨

・・・ということで今回はLaravelGraphQLを使う方法をご紹介します。
ぜひ楽しみながらやってみましょう❗

「読み方は、(ぐらふきゅーえる)です😊」

開発環境: Laravel 7.x

やりたいこと

今回は各ユーザーが自由に書き込みができるpostsというテーブルを作り、GraphQLで本人が投稿したデータだけを取得してリスト表示します。

前提として

Laravelにログイン機能がインストールされ、さらにユーザーデータが用意されていることが前提です。

詳しくは「Laravel6.x以降でログイン機能をインストールする方法」をご覧ください。

パッケージをインストールする

LaravelGraphQLを使えるようにするパッケージをインストールします。

composer require nuwave/lighthouse

準備する

パッケージからLaravel側にファイルをコピーします。

構成ファイル

まずは構成ファイルです。
以下のコマンドを実行してください。

php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=schema

これで、ファイルが作成されました。

/graphql/schema.graphql

そして、このファイルの中で「どんな操作を受け付けるか」、また「どんなデータを返すか」を設定することになります。

では、今回postsデータを取得するための設定をしておきましょう。

/graphql/schema.graphql

"GraphQLのアクセス設定"
type Query {
    posts(page: Int): [Post!]! @paginate(defaultCount: 3)
}

"postsテーブルの取得内容"
type Post {
    id: ID!
    user_id: Int!
    title: String!
    content: String!
    created_at: String!
    updated_at: String!
}

この内容を大まかに説明すると、postsにアクセスすると、postsのデータを1ページ3件ごとの配列で返すという意味になります。

設定ファイル

次に設定ファイルです。
以下のコマンドを実行してください。

php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=config

これで設定ファイルも作成されました。

/config/lighthouse.php

今回はログイン・ユーザーの情報が必要になるので、ミドルウェアにwebを追加しておきましょう。

/config/lighthouse.php

'middleware' => [
    'web', // 👈 追加
    
    // 省略

],

モデル&マイグレーションをつくる

postsテーブルが使えるように、以下のコマンドでモデルとマイグレーションを作成します。

php artisan make:model Post -m

これで、2つのファイルが作成されました。
それぞれ中身に必要な情報を追加します。

/app/Post.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    // グローバル・スコープ
    protected static function booted() { // 👈 追加
    
        static::addGlobalScope('only_user', function (Builder $builder) {

            $user_id = -1;

            if(auth()->check()) {

                $user_id = auth()->user()->id;

            }

            $builder->where('user_id', $user_id);

        });
    }
}

この中のグローバル・スコープは、ログイン中のユーザーが投稿したデータだけを取得するために追加しています。(つまり、他人のデータを見ることはできなくなります)

続いてマイグレーションです。
今回はテストですのでシンプルなテーブルにします。

/home/sukohi/php-/database/migrations/****_**_**_******_create_posts_table.php

// 省略

public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->unsignedBigInteger('user_id');
        $table->string('title');
        $table->text('content');
        $table->timestamps();

        $table->foreign('user_id')->references('id')->on('users');
    });
}

// 省略

マイグレーションが完了したらテストデータです。
次のコマンドを実行してSeederをつくりましょう。

php artisan make:seed PostsTableSeeder

そして、中身を以下のようにします。

/database/seeds/PostsTableSeeder.php

// 省略

public function run()
{
    $user_ids = \App\User::pluck('id');

    for($i = 0 ; $i < 100 ; $i++) {

        $post = new \App\Post();
        $post->user_id = $user_ids->random();
        $post->title = 'テストタイトル - '. $i;
        $post->content = "テストコンテンツ\nテストコンテンツ\nテストコンテンツ\n";
        $post->save();

    }
}

// 省略

では、以下のコマンドでマイグレーションを実行します。

php artisan migrate:fresh --seed

これで、postsテーブルは次のようになります。

ルートをつくる

今回はテストですので、簡略して書きますが実際にはPostControllerなどを作ってください。

/routes/web.php

Route::get('post', function(){ return view('post.index'); });

ビューをつくる

では、実際にGraphQLでデータ取得するビューをつくりましょう。

<html>
<head>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
</head>
<body>
    <div id="app">
        <!-- リスト表示 -->
        <table class="table table-striped">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>タイトル</th>
                    <th>内容</th>
                    <th>作成日時</th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="post in posts">
                    <td v-text="post.id"></td>
                    <td v-text="post.title"></td>
                    <td style="white-space: pre-wrap" v-text="post.content"></td>
                    <td v-text="post.created_at"></td>
                </tr>
            </tbody>
        </table>
        <!-- ページ移動 -->
        <div class="p-3">
            <ul class="pagination pg-blue">
                <li class="page-item" v-for="page in paginatorInfo.lastPage">
                    <a href="#" class="page-link" v-text="page" @click="movePage(page)"></a>
                </li>
            </ul>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
    <script>

        new Vue({
            el: '#app',
            data: {
                posts: [],
                paginatorInfo: {},
                page: 1
            },
            methods: {
                getPosts() { // 👈 データ取得 ・・・ ①

                    const params = {
                        query:
                            `{
                                posts(page: ${this.page}) {
                                    data {
                                        id
                                        title
                                        content
                                        created_at
                                    }
                                    paginatorInfo {
                                        currentPage
                                        lastPage
                                    }
                                }
                            }`
                    };
                    axios.post('/graphql', params)
                        .then(response => {

                            const posts = response.data.data.posts;
                            this.posts = posts.data;
                            this.paginatorInfo = posts.paginatorInfo;

                        });

                },
                movePage(page) { // 👈 ページ移動 ・・・ ②

                    this.page = page;
                    this.getPosts();

                }
            },
            mounted() {

                this.getPosts();

            }
        });

    </script>
</body>
</html>

この中でやっていることは、次のとおりです。

① データ取得

ここでAjax + GraphQLpostsデータを取得しています。
この中で、実際に送信される内容は例えば、次のようなものになります。

{
    posts(page: 1) { // 👈 ページは可変
        data {
            id
            title
            content
            created_at
        }
        paginatorInfo {
            currentPage
            lastPage
        }
    }
}

また、取得されるデータのサンプルはこのようになります。

"posts": {
    "data": [
        {
            "id": "2",
            "title": "テストタイトル - 1",
            "content": "(長いので省略)",
            "created_at": "2020-04-04 09:56:12"
        },
        // たくさんあるので省略
    ],
    "paginatorInfo": {
        "currentPage": 1,
        "lastPage": 4
    }
}

また、今回ページ移動の部分は以下2つデータを取得しています。

  • currentPage: 現在のページ
  • lastPage: 最後のページ

実際にはこのようなデータが取得できます。

paginatorInfo: {
    currentPage: 1, 
    lastPage: 4
}

② ページ移動

ページ移動は、page変数の中身を変更しgetPosts()を実行するだけで完了できます。

テストしてみる

では実際にテストしてみましょう!
まずは「http://******/post」にアクセスしたところです。

データの一覧とページ移動リンクが表示されています。(しかも表示されているのはログインユーザーが投稿したものだけです)

では、2ページ目に移動してみましょう。

はい!
表示内容が入れ替わりました。

成功です😊✨

おまけ:テストに便利なツール

GraphQLをテストで試すことができる「laravel-graphql-playground」という便利なツールが公開されています。

これは、GraphQLを書いて送信するだけで「どんな結果になるか❓」がわかるスグレモノです。

インストールは以下のコマンド一発でOKです。

composer require mll-lab/laravel-graphql-playground

なお、419エラーが出る場合は、ミドルウェアでcsrfを無効にする必要があります。

/app/Http/Middleware/VerifyCsrfToken.php

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    protected $except = [
        '/graphql' // 👈 追加
    ];
}
開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで、今回はLaravelGraphQLを使ってみました。

ちなみに、GraphQLを初めて使ったわけですが、「特にデータ取得に向いている❗」と感じました。

冒頭でも書いたとおり、これまでは1つずつテーブルごとに取得していたのものを一気に取得できるのはとても魅力的です。(実際問題これを解決するために独自パッケージをつくったりもしてました😅)

逆に「うーん・・・💦」となったのは、バリデーションの部分です。もちろんGraphQLにもバリデーション機能はあるのですが、正直なところ、バリデーションはLaravelのものをそのまま使うほうが開発効率は高いと感じました。

また、少し複雑なことをしようとするとコードの可読性が一気に悪くなるので、個人的には、以下のような固定されたデータを取得する場合なら使いたいかな、という印象です。

  • 都道府県データ
  • 事業所データ
  • はい/いいえ/未定

などなど。

ただ、今後もGraphQLには注目していきたいと思います😊✨
ぜひ皆さんも試してみてくださいね。

ではでは〜!

「なぜか、”ぐらふきゅーえる”って、
何回も言いたくなりませんか❓😁」

このエントリーをはてなブックマークに追加       follow us in feedly