vue3とlaravel8でリアルタイムチャット機能を実装

## はじめに
今回は、Pusherを使ったリアルタイムチャット機能を実装していきます!!

この記事の目標は、ブラウザを２つ開いて片方からチャットを送るとリロードしなくても自動更新される簡単なリアルタイムチャット機能を目指します!

#### 実行環境
Vue : 3.0.5
Laravel : 8.63.0
## まず簡単なチャット機能を作る(リアルタイムではない)
今回の本題は、リアルタイムということなのでここはサクッと進みたいと思います。
#### テーブルとモデルの作成
まずチャットのメッセージを保存するためのテーブルを作ります。
```c#:コマンド
php artisan make:model Message
php artisan make:migration create_messages_table --create=messages
```
```c#:src/database/migrations/2022_03_00_000000_create_messages_table.php
public function up()
{
    Schema::create('messages', function (Blueprint $table) {
        table->bigIncrements('id');
        $table->text('body');//追記
        $table->timestamps();
    });
}
```
```c#:src/app/Models/Message.php
class ChatMessage extends Model
{
    protected $guarded = ['id'];//追記
    protected $table = 'chat_messages';//追記
}
```
```c#:コマンド
php artisan migrate
```

これでチャットのメッセージを保存するためのテーブルが完成です。
#### チャットページの作成
次に、チャットページを作っていきます。(ここも同じくサクッと進みます)

ルートを追加
```c#:src/routes/web.php
Route::get('/chat', 'ChatController@index');
Route::post('/chat', 'ChatController@create');
```
コントローラーを追加
```c#:コマンド
php artisan make:controller ChatController
```
```c#:src/app/Http/Controllers/ChatController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Message;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;

class ChatController extends Controller
{
    // 新着順にメッセージ一覧を取得
    public function index() {

        $messages = Message::orderBy('id', 'desc')->get();
        return Inertia::render(
            'Chat',
            ['messages' => $messages]
        );
    }

    // メッセージを登録
    public function create(Request $request) { 

        DB::beginTransaction();
        try {

            $message = new Message;
            $message->text = $request->message;
            $message->save();

            DB::commit();
        } catch (\PDOException $e) {
            \Log::debug($e->getMessage());
            DB::rollback();
        }
    }
}
```
ページを追加
```c#:src/resources/js/Pages/Chat.vue
<template>
    <div>
        <div>
            <textarea v-model="message"></textarea><br>
            <button type="button" v-on:click="send()">送信</button>
        </div>

        <div v-for="m in messages" :key="m.id">
            <span v-text="m.text"></span>
        </div>
    </div>
</template>
<script>
    export default {
        props: {
            messages: Array,
        },
        data: function() {
            return {
                message: '',
            }
        },
        methods: {
            send() {
                const formData = new FormData()
                formData.append('message',this.message)

                this.$inertia.post('/chat', formData, {
                    onSuccess: () => (this.message = '';),
                })
            },
        },
    }
</script>
```
サクッとですがこれでリアルタイムではないチャット機能の完成です!!

## チャット機能をリアルタイムにしていく
ここからが本題です!!
今作ったチャット機能をリロード無しで自動的に更新されるリアルタイムチャットにしていきます!!

今回は、Pusherを使うので、Pusherに登録して.envに必要なPusherのAppKeysを取得します。
https://pusher.com/

PusherのAppKeysを追加、編集
```c#:src/.env
BROADCAST_DRIVER=pusher

PUSHER_APP_ID=取得したKey
PUSHER_APP_KEY=取得したKey
PUSHER_APP_SECRET=取得したKey
PUSHER_APP_CLUSTER=取得したKey

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
```
```c#:src/config/app.php
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        App\Providers\BroadcastServiceProvider::class,//ここを解除
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,
        App\Providers\FortifyServiceProvider::class,
        App\Providers\JetstreamServiceProvider::class,
        NotificationChannels\WebPush\WebPushServiceProvider::class,
```
デフォルトではBroadcastServiceProviderがコメント化されているので、これを解除してあげます。

```c#:コマンド
composer require pusher/pusher-php-server "~3.0"
npm install --save laravel-echo pusher-js
```
上記のコマンドPusher用のパッケージと、Pusherとのつなぎ役をしてくれるLaravelEchoをインストールします。
```c#:src/resources/js/bootstrap.js
import Echo from 'laravel-echo';

window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    forceTLS: true
});
```
インストールが完了後、bootstrap.jsの上記の部分もデフォルトではコメント化されているので、これを解除してあげます。
```c#:コマンド
npm run dev
```
ビルドして更新しておきます。

#### イベントの作成
```c#:コマンド
php artisan make:event MessageCreated
```
上記のコマンドでイベント用のファイルを用意します。

```c#:src/app/Events/MessageCreated.php
<?php

namespace App\Events;

use App\Message;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class MessageCreated implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $message;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(Message $message)
    {
        $this->message = $message;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('chat');
    }
}
```
これでイベント用のファイルができたので、メッセージが送信された先のコントローラーにこのイベントが呼ばれるようにしていきましょう!

```c#:src/app/Http/Controllers/ChatController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Message;
use App\Events\MessageCreated;//追記
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;

class ChatController extends Controller
{
    // 新着順にメッセージ一覧を取得
    public function index() {

        $messages = Message::orderBy('id', 'desc')->get();
        return Inertia::render(
            'Chat',
            ['messages' => $messages]
        );
    }

    // メッセージを登録
    public function create(Request $request) { 

        DB::beginTransaction();
        try {

            $message = new Message;
            $message->text = $request->message;
            $message->save();

            event(new MessageCreated($message));//追記
            DB::commit();
        } catch (\PDOException $e) {
            \Log::debug($e->getMessage());
            DB::rollback();
        }
    }
}
```
では、最後にvue側にもPusherからの通知を待機するLaravelEchoのコードをmounted()内に追加してビルドしたら作業は終了です。

```c#:src/resources/js/Pages/Chat.vue
<template>
    <div>
        <div>
            <textarea v-model="message"></textarea><br>
            <button type="button" v-on:click="send()">送信</button>
        </div>

        <div v-for="m in messages" :key="m.id">
            <span v-text="m.text"></span>
        </div>
    </div>
</template>
<script>
    export default {
        props: {
            messages: Array,
        },
        data: function() {
            return {
                message: '',
            }
        },
        mounted() { //追記
            Echo.channel('chat')//追記
                .listen('MessageCreated', (e) => {//追記
                    this.getMessages();//追記
                });//追記
        },//追記
        methods: {
            getMessages() {//追記
                this.$inertia.get('/chat');//追記
            },//追記
            send() {
                const formData = new FormData()
                formData.append('message',this.message)

                this.$inertia.post('/chat', formData, {
                    onSuccess: () => (this.message = '';),
                })
            },
        },
    }
</script>

```
```c#:コマンド
npm run dev
```

リアルタイムチャットの実装は以上となります!!
#### 確認方法
確認方法は、２種類のブラウザ(chromeとfirefox)を用意して確認するか、
同じブラウザでも片方は、シークレットモードにすれば確認できるはずです!!

片方からメッセージを送信しリロードしなくても自動でもう片方のメッセージが更新されたら成功です!!

お疲れ様でした!!

最後まで読んでくれてありがとうございます!!
どこか途中でうまく行かなかった場合は、気軽にコメントorツイッター(@LC_Ono)のDMでも対応します!