Laravel ReverbはWebSocketを利用してLaravelサーバとブラウザ間でリアルタイムにデータの送受信を行うことができるWebSocketサーバです。Pusherなどの商用のサービスを使わずにLaravelと一緒に利用することができます。

実際にローカルで動作確認を行う前にLaravel Reverbを利用するとどのような流れでリアルタイムにデータの送受信を行うのか簡単に説明しておきます。

  • Laravelのアプリケーションでイベントを実行
  • 実行されたイベントをLaravel Reverbのサーバが受信
  • Laravel Reverbサーバからブラウザに受け取ったデータを送信
  • ブラウザはLaravel Reverbサーバから受け取ったデータをJavaScriptを利用してブラウザのリロードを行うことなくブラウザ上に反映

Laravel ReverbはLaravelのBroadcasting, Event, Queue, Laravel Echoの機能と一緒に利用するのでこれらの知識も必要になりますが本文書に沿って設定を行うことで上記の説明もすっきり理解できるようになります。

少し古い記事ですが公開済みの記事でPusher(商用サービス)を利用したWebSocketによるリアルタイムのデータを送受信をLaravel+Vue.jsで設定しています。基本的な動作についてはLaravel Reverbでも違いありません。

macOS上でLaravel11を利用して動作確認を行っています。

プロジェクトの作成

OS上の任意のディレクトリでcomposerコマンドを利用してLaravelのプロジェクトを作成します。


% composer create-project laravel/laravel laravel11_reverb
//略

   INFO  Preparing database.  

  Creating migration table .................. 3.70ms DONE

   INFO  Running migrations.  

  0001_01_01_000000_create_users_table ....... 6.59ms DONE
  0001_01_01_000001_create_cache_table ....... 2.34ms DONE
  0001_01_01_000002_create_jobs_table ........ 5.36ms DONE

インストールしたLaravelのバージョンを確認しておきます。


 % php artisan -V
Laravel Framework 11.0.5

Reverbのインストール

プロジェクトの作成が完了したらLaravel Reverbのインストールを行います。Laravel 11ではLaravel Reverbを利用する際に必須となるBroadcastingの機能はデフォルトではインストールされていないのでインストールを行います。Broadcastingのインストールと一緒にLaravel Reverbをインストールするか、ブラウザ側で利用するJavaScriptのパッケージのインストールするかの確認が行われるのでどちらも”Yes”を選択します。


% php artisan install:broadcasting

   INFO  Published 'broadcasting' configuration file.

   INFO  Published 'channels' route file.

 ┌ Would you like to install Laravel Reverb? ───────────────────┐
 │ Yes                                                          │
 └──────────────────────────────────────────────────────────────┘

./composer.json has been updated
Running composer update laravel/reverb
//略
   INFO  Reverb installed successfully.  

 ┌ Would you like to install and build the Node dependencies required for broadcasting? ┐
 │ ● Yes / ○ No                                                                         │
 └──────────────────────────────────────────────────────────────────────────────────────┘
//略

JavaScriptの確認

インストールしたJavaScriptにはどのようなものがあるか確認するためにpackage.jsonファイルの確認を行っておきます。


{
    "private": true,
    "type": "module",
    "scripts": {
        "dev": "vite",
        "build": "vite build"
    },
    "devDependencies": {
        "axios": "^1.6.4",
        "laravel-echo": "^1.16.0",
        "laravel-vite-plugin": "^1.0",
        "pusher-js": "^8.4.0-rc2",
        "vite": "^5.0"
    }
}

デフォルトから指定されているパッケージに加えてlaravel-echoとpusher-jsが追加されています。pusher-jsとありますがPusherで利用するパッケージをReverbでも利用しています。

それらのパッケージがどのようにあ利用されているかも確認しておきます。resource/js/bootstrap.jsファイルを見るとecho.jsファイルのimportが追加されています。


import axios from 'axios';
window.axios = axios;

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

/**
 * Echo exposes an expressive API for subscribing to channels and listening
 * for events that are broadcast by Laravel. Echo and event broadcasting
 * allow your team to quickly build robust real-time web applications.
 */

import './echo';

echo.jsファイルの中身を確認するとインストールしたlaravel-echoとpusher-jsがimportされています。2つのパッケージが利用させていることがわかりました。


import Echo from 'laravel-echo';

import Pusher from 'pusher-js';
window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'reverb',
    key: import.meta.env.VITE_REVERB_APP_KEY,
    wsHost: import.meta.env.VITE_REVERB_HOST,
    wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});

Reverbに関する設定の確認

Broadcastingをインストールすることでconfigディレクトリにbroadcastiong.phpファイル, routesディレクトリにchannels.phpファイルが作成されます。

Reverbをインストールすることでconfigディレクトリにreverb.phpファイルが作成されます。

.envファイルの中にはReverbに関する環境変数が設定されています。値はすべて自動で設定が行われています。本文書ではこの環境変数の値の変更は行いません。


//略
REVERB_APP_ID=817504
REVERB_APP_KEY=jjpmxh3bivbcncbqgpfd
REVERB_APP_SECRET=jeneqjvuyunytw3yocmv
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

.envに設定されている環境変数はreverb.phpとbroadcating.phpファイルで利用されています。


<?php

return [

    /*
    |--------------------------
    | Default Reverb Server
    |--------------------------
//略
    */

    'default' => env('REVERB_SERVER', 'reverb'),

    /*
    |--------------------------
    | Reverb Servers
    |--------------------------
//略
    */

    'servers' => [

        'reverb' => [
            'host' => env('REVERB_SERVER_HOST', '0.0.0.0'),
            'port' => env('REVERB_SERVER_PORT', 8080),
            'hostname' => env('REVERB_HOST'),
            'options' => [
                'tls' => [],
            ],
            'scaling' => [
                'enabled' => env('REVERB_SCALING_ENABLED', false),
                'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'),
            ],
            'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15),
        ],

    ],

    /*
    |--------------------------
    | Reverb Applications
    |--------------------------
//略
    */

    'apps' => [

        'provider' => 'config',

        'apps' => [
            [
                'key' => env('REVERB_APP_KEY'),
                'secret' => env('REVERB_APP_SECRET'),
                'app_id' => env('REVERB_APP_ID'),
                'options' => [
                    'host' => env('REVERB_HOST'),
                    'port' => env('REVERB_PORT', 443),
                    'scheme' => env('REVERB_SCHEME', 'https'),
                    'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
                ],
                'allowed_origins' => ['*'],
                'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60),
                'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10000),
            ],
        ],

    ],

];

<?php

return [

    /*
    |--------------------------
    | Default Broadcaster
    |--------------------------
//略
    */

    'default' => env('BROADCAST_CONNECTION', 'null'),

    /*
    |--------------------------
    | Broadcast Connections
    |--------------------------
//略
    */

    'connections' => [

        'reverb' => [
            'driver' => 'reverb',
            'key' => env('REVERB_APP_KEY'),
            'secret' => env('REVERB_APP_SECRET'),
            'app_id' => env('REVERB_APP_ID'),
            'options' => [
                'host' => env('REVERB_HOST'),
                'port' => env('REVERB_PORT', 443),
                'scheme' => env('REVERB_SCHEME', 'https'),
                'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
            ],
            'client_options' => [
                // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
            ],
        ],

        'pusher' => [
            'driver' => 'pusher',
//略
        ],

        'ably' => [
            'driver' => 'ably',
            'key' => env('ABLY_KEY'),
        ],

        'log' => [
            'driver' => 'log',
        ],

        'null' => [
            'driver' => 'null',
        ],

    ],

];

Eventの作成

Eventを実行してBroadcastingすることでLaravel ReverbにChannelを通してデータを送信するのでまずEventを作成します。名前はTestEventとしています。


% php artisan make:event TestEvent

   INFO  Event [app/Events/TestEvent.php] created successfully.

実行するとappディレクトリにEventsディレクトリが作成され、その下にTestEvent.phpファイルが作成されます。


<?php

namespace App\Events;

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

class TestEvent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('channel-name'),
        ];
    }
}

デフォルトでは未使用のShouldBroadcastをimplementsで継承する必要があります。デフォルトではPrivateChannelを設定していますが動作確認なので認証なしで受け取れるChannelに変更します。Channelの名前もtest-channelに変更しておきます。データを受信するブラウザ側ではこのChannelを利用してデータの受信を行います。


<?php

namespace App\Events;

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

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

//略
    public function broadcastOn(): array
    {
        return [
            new Channel('test-channel'),
        ];
    }
}

Broadcastingの動作確認

Broadcatingの設定ではconfig/broadcasting.phpファイルのdriverでどのサービス(Laravel Reverb, Pusherなど)を利用するか指定することができます。動作確認用にlogというdriverも準備されているので.envファイルのBROADCAST_CONNECTIONの値をreverbからlogに変更することでlog driverを利用することができます。logに設定するとBroadcastingされたイベントの内容をログファイルで確認することができます。logでの動作確認後にBROADCAST_CONNECTIONの値をreverbに戻します。


BROADCAST_CONNECTION=reverb

作成したTestEventをweb.phpファイルで実行できるように設定します。”/”にアクセスがあると必ず実行されます。通常は何かの処理が完了した後にイベントを実行します。


<?php

use App\Events\TestEvent;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    TestEvent::dispatch();
    return view('welcome');
});

開発サーバを起動してブラウザからlocalhost:8000にアクセスを行います。アクセスが完了したらstorage/logs/laravel.logファイルを確認します。


% php artisan serve

   INFO  Server running on [http://127.0.0.1:8000].

  Press Ctrl+C to stop the server

ブラウザ上にはWelcomeページが表示されていますがログファイルには何も記述されません。

本文書通りにここまで設定を行っていればstorage/logsディレクトリにはlaravel.logファイルが存在しません。
fukidashi

本文書ではTablePlusというデータベース管理ソフトウェアを利用してSQLiteデータベースにアクセスを行いjobsテーブルを確認します。payloadの列にApp\\Events\\TesxtEventの名前を確認することができます。jobsテーブルには先ほど実行したイベントが登録されています。

jobsテーブルの確認
jobsテーブルの確認

直接SQLiteデータベースにアクセスを行いテーブルの情報を確認したい場合はこちらの記事が参考になります。

Queue Workerの起動

jobsテーブルに登録されたイベントを実行するためにphp artisan queue:workを実行します。コマンドを実行した瞬間にテーブルに登録されていたい処理が実行されます。


% php artisan queue:work

   INFO  Processing jobs from the [default] queue.

  2024-03-14 02:02:02 App\Events\TestEvent ............ RUNNING
  2024-03-14 02:02:02 App\Events\TestEvent ............ 21.29ms DONE

再度laravel.logファイルを確認すると実行したEventの情報が表示されていることが確認できます。ファイルが作成されていない場合はここで作成されます。


 % tail -f storage/logs/laravel.log 
[2024-03-14 02:02:02] local.INFO: Broadcasting [App\Events\TestEvent] on channels [test-channel] with payload:
{
    "socket": null
}  

jobsテーブルでは処理が完了するとEvent情報がなくなり空になります。

ここまでの動作確認でEventを利用してBroadcastingが正常に動作していることを確認できました。

logを利用した動作確認が完了したのでBROADCAST_CONNECTIONの値をlogからreverbに戻します。


BROADCAST_CONNECTION=reverb

.envファイルを更新後はphp artisan serveコマンドを再起動しておきます。

Reverbサーバの起動

Reverbサーバの起動を行うために”php artisan reverb:start”コマンドを実行します。


% php artisan reverb:start

   INFO  Starting server on 0.0.0.0:8080 (localhost).

先ほどの動作確認と同様にブラウザからアクセスを行います。php artisan queue:workを実行しているのでEventの処理のメッセージは表示されます。

storage/logs/laravel.logにEventのメッセージが表示される場合は.envの変更が反映されていないので起動している開発サーバを再起動してください。
fukidashi

しかし、Reverbを起動したターミナルにも何もメッセージは表示されないため何が行っているのかわかりません。

ブラウザ側でのデータの受信

実際にReverbサーバが動作しているのか確認するためにブラウザ側でReverbサーバから送信されたデータが受信できるのか確認するために設定を行っていきます。

ブラウザ側での受信設定はJavaScriptで行うためresource/js/bootstrap.jsファイルに以下の設定を追加します。Echo.channelの引数にはTestEventのChannelの引数で設定したChannel名とlistenメソッドの引数にはEventの名前を指定します。


import axios from "axios";
window.axios = axios;

window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";

/**
 * Echo exposes an expressive API for subscribing to channels and listening
 * for events that are broadcast by Laravel. Echo and event broadcasting
 * allow your team to quickly build robust real-time web applications.
 */

import "./echo";

Echo.channel("test-channel").listen("TestEvent", function (e) {
    console.log("received a message");
});

welcome.blade.phpファイルでJavaScriptファイルを読み込めるように以下のコードをheadタグの閉じタグの前に設定します。


//略
   </style>
   @vite('resources/js/app.js')
</head>
//略

更新したbootstrap.jsファイルの内容を反映させるためにViteの開発サーバを起動します。


 % npm run dev

> dev
> vite


  VITE v5.1.6  ready in 483 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

  LARAVEL v11.0.5  plugin v1.0.2

  ➜  APP_URL: http://localhost

ブラウザからアクセスを行うとデベロッパーのコンソールに”received a message”が表示されます。

ブラウザ上でデータを受け取った確認
ブラウザ上でデータを受け取った確認

ブラウザ上でLaravel Reverbを経由してデータが受信できることが確認できました。

Reverbサーバを起動したターミナルには特に何も表示されないのでdebugを有効するために一度Reverbサーバを停止して–debugオプションをつけて起動を行います。ブラウザからアクセスするとメッセージが表示されるようになりました。


 % php artisan reverb:start --debug

   INFO  Starting server on 0.0.0.0:8080 (localhost).

  Connection Established ...... 837129120.563908880
  Message Received ............ 837129120.563908880

   1▕ {
   2▕     "event": "pusher:subscribe",
   3▕     "data": {
   4▕         "auth": "",
   5▕         "channel": "test-channel"
   6▕     }
   7▕ }

  Message Handled ............. 837129120.563908880

メッセージの受信

ブラウザ側でTestEvent.phpファイルに設定したメッセージを受信する方法を確認します。TestEvent.phpファイルで$messageを定義して”Hello, world!”を設定します。


//略
class TestEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $message;

    /**
     * Create a new event instance.
     */
    public function __construct()
    {
        $this->message = 'Hello, world!';
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new Channel('test-channel')
        ];
    }
}

bootstrap.jsで受信したmessageをconsole.logで表示させるように設定します。



Echo.channel("test-channel").listen("TestEvent", function (e) {
    console.log("received a message", e.message);
});

ブラウザ側では受信したメッセージがコンソールに表示されます。

ブラウザ側で受信したメッセージの表示
ブラウザ側で受信したメッセージの表示

ブラウザ上でのメッセージの表示

受信したメッセージをブラウザ上に表示できるようにwelcome.blade.phpファイルを更新します。idにmessageを持つdiv要素を追加します。


<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Laravel</title>
        @vite('resources/js/app.js')
    </head>
    <body>
        <h1>Laravel Reverbからのメッセージの受信</h1>
        <div id="message"></div>
    </body>
</html>

bootstrap.jsファイルではdocument.getElementById(“message”)でdiv要素にアクセスしてtextContextプロパティに受信したデータを設定します。


Echo.channel("test-channel").listen("TestEvent", function (e) {
    document.getElementById("message").textContent = e.message;
});

設定後にブラウザからアクセスするとページを開いてから暫くして”Hello, world!”がブラウザ上に表示されます。

受信したメッセージの表示
受信したメッセージの表示

ここまでの設定でLaravelアプリケーションで実行されたEventによって送信されたデータをLaravel Reverbが受け取り、受け取ったデータをブラウザに送信してブラウザ側で受信することができるようになりました。

簡単なChat機能の追加

ここではinput要素に文字列を入力して送信ボタンを押したらアクセスしているブラウザすべてで入力したメッセージをリアルタイムで表示する簡単なChat機能の追加を行います。表示するためにページのリロードは行いません。

welcome.blade.phpファイルに入力フォームとボタンを追加し、メッセージはul, liタグを利用してリスト化していきます。


<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Laravel</title>
        @vite('resources/js/app.js')
    </head>
    <body>
        <h1>Laravel Reverbからのメッセージの受信</h1>
        <div>
            <label for="message">メッセージ:</label>
            <input type="text" id="message" name="message">
            <button id="send">送信</button>
        </div>
        <ul id="messages"></ul>
    </body>
</html>

Reverbから受信したデータをli要素を作成してul要素に追加しています。


Echo.channel("test-channel").listen("TestEvent", function (e) {
    const newMessage = document.createElement("li");
    newMessage.textContent = e.message;
    const ul = document.getElementById("messages");
    ul.appendChild(newMessage);
});

bootstap.jsではbuttonにclickイベントを設定してボタンが押されたらinput要素に入力したテキストを取得し、”/”にPOSTリクエストと一緒に送信します。


document.getElementById("send").addEventListener("click", function () {
    const message = document.getElementById("message").value;
    if (!message) return;
    axios.post("/", { message: message }).then(() => {
        document.getElementById("message").value = "";
    });
});

web.phpに”/”のルーティングを追加します。


<?php

use App\Events\TestEvent;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    // TestEvent::dispatch();
    return view('welcome');
});

Route::post('/', function (Request $request) {
    TestEvent::dispatch($request->message);
    return response()->json(['message' => 'Event has been sent!']);
});

2つのブラウザを起動してどちらかのinput要素に文字列を入力するとどちらのブラウザ上にも表示されます。

複数のブラウザでメッセージを表示
複数のブラウザでメッセージを表示

簡単なChat機能を追加できました。ここまでの設定でLaravel Reverbがどのような機能なのか理解できたのではないでしょうか。