はじめに
現代のWebアプリケーションでは、リアルタイム通信機能が不可欠になってきています。特にチャットアプリケーションでは、メッセージのリアルタイム配信がユーザー体験を大きく向上させます。この記事では、Laravel WebSocketsパッケージを使用して、シンプルながらも機能的なリアルタイムチャットアプリケーションを構築する方法を解説します。
前提条件
- PHP 8.1以上
- Composer
- Laravel 10.x
- Node.js と npm
プロジェクトのセットアップ
まず、新しいLaravelプロジェクトを作成しましょう:
composer create-project laravel/laravel laravel-websockets-chat
cd laravel-websockets-chat
必要なパッケージのインストール
Laravel WebSocketsと関連パッケージをインストールします:
composer require beyondcode/laravel-websockets pusher/pusher-php-server
npm install laravel-echo pusher-js
WebSocketsの設定
1. 環境設定
.env
ファイルを編集して、Pusher関連の設定を行います:
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=laravel-websockets
PUSHER_APP_KEY=local-key
PUSHER_APP_SECRET=local-secret
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_SCHEME=http
PUSHER_APP_CLUSTER=mt1
2. WebSocketsの設定公開
Laravel WebSocketsの設定ファイルを公開します:
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"
3. 認証設定
config/broadcasting.php
ファイルのPusher設定を確認し、必要に応じて編集します:
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'host' => env('PUSHER_HOST', '127.0.0.1'),
'port' => env('PUSHER_PORT', 6001),
'scheme' => env('PUSHER_SCHEME', 'http'),
'encrypted' => false,
'useTLS' => false,
],
],
データベースの設定
1. マイグレーションの実行
WebSocketsのマイグレーションを実行します:
php artisan migrate
2. メッセージモデルの作成
チャットメッセージを保存するためのモデルとマイグレーションを作成します:
php artisan make:model Message -m
database/migrations/{timestamp}_create_messages_table.php
を編集します:
public function up()
{
Schema::create('messages', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained();
$table->string('message');
$table->timestamps();
});
}
app/Models/Message.php
を編集します:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Message extends Model
{
use HasFactory;
protected $fillable = ['user_id', 'message'];
public function user()
{
return $this->belongsTo(User::class);
}
}
イベントの作成
新しいメッセージを送信するためのイベントを作成します:
php artisan make:event NewMessage
app/Events/NewMessage.php
を編集します:
<?php
namespace App\Events;
use App\Models\Message;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class NewMessage implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
public function __construct(Message $message)
{
$this->message = $message;
}
public function broadcastOn()
{
return new Channel('chat');
}
public function broadcastWith()
{
return [
'id' => $this->message->id,
'user_id' => $this->message->user_id,
'username' => $this->message->user->name,
'message' => $this->message->message,
'created_at' => $this->message->created_at->format('Y-m-d H:i:s')
];
}
}
コントローラーの作成
メッセージを処理するコントローラーを作成します:
php artisan make:controller ChatController
app/Http/Controllers/ChatController.php
を編集します:
<?php
namespace App\Http\Controllers;
use App\Events\NewMessage;
use App\Models\Message;
use Illuminate\Http\Request;
class ChatController extends Controller
{
public function index()
{
$messages = Message::with('user')->orderBy('created_at', 'asc')->get();
return view('chat', compact('messages'));
}
public function store(Request $request)
{
$validated = $request->validate([
'message' => 'required|string|max:255',
]);
$message = Message::create([
'user_id' => auth()->id(),
'message' => $validated['message'],
]);
$message->load('user');
broadcast(new NewMessage($message))->toOthers();
return response()->json($message);
}
}
ルーティングの設定
routes/web.php
を編集してルートを追加します:
<?php
use App\Http\Controllers\ChatController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::middleware(['auth'])->group(function () {
Route::get('/chat', [ChatController::class, 'index'])->name('chat');
Route::post('/chat', [ChatController::class, 'store'])->name('chat.store');
});
require __DIR__.'/auth.php';
認証の設定
Laravel Breezeを使って簡単な認証システムをセットアップします:
composer require laravel/breeze --dev
php artisan breeze:install blade
php artisan migrate
npm install
npm run build
フロントエンドの設定
1. Laravel Echoの設定
resources/js/bootstrap.js
を編集して、Laravel Echoの設定を追加します:
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'pusher',
key: import.meta.env.VITE_PUSHER_APP_KEY,
wsHost: import.meta.env.VITE_PUSHER_HOST || window.location.hostname,
wsPort: import.meta.env.VITE_PUSHER_PORT || 6001,
forceTLS: false,
disableStats: true,
});
2. ビューの作成
resources/views/chat.blade.php
を作成します:
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('チャット') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div id="chat-messages" class="mb-4 h-96 overflow-y-auto p-4 bg-gray-100 rounded">
@foreach($messages as $message)
<div class="message mb-2 p-2 rounded @if($message->user_id === auth()->id()) bg-blue-100 ml-auto w-3/4 @else bg-gray-200 mr-auto w-3/4 @endif">
<div class="font-bold">{{ $message->user->name }}</div>
<div>{{ $message->message }}</div>
<div class="text-xs text-gray-500">{{ $message->created_at->format('Y-m-d H:i:s') }}</div>
</div>
@endforeach
</div>
<form id="chat-form">
@csrf
<div class="flex">
<input type="text" id="message" name="message" class="flex-1 rounded-l border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" placeholder="メッセージを入力...">
<button type="submit" class="px-4 py-2 bg-blue-500 text-white rounded-r hover:bg-blue-600">送信</button>
</div>
</form>
</div>
</div>
</div>
</div>
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const chatMessages = document.getElementById('chat-messages');
const chatForm = document.getElementById('chat-form');
const messageInput = document.getElementById('message');
// スクロールを一番下に移動
function scrollToBottom() {
chatMessages.scrollTop = chatMessages.scrollHeight;
}
// ページ読み込み時にスクロールを一番下に移動
scrollToBottom();
// メッセージの追加
function addMessage(message, isMine = false) {
const messageDiv = document.createElement('div');
messageDiv.className = `message mb-2 p-2 rounded ${isMine ? 'bg-blue-100 ml-auto w-3/4' : 'bg-gray-200 mr-auto w-3/4'}`;
const nameDiv = document.createElement('div');
nameDiv.className = 'font-bold';
nameDiv.textContent = message.username;
const contentDiv = document.createElement('div');
contentDiv.textContent = message.message;
const timeDiv = document.createElement('div');
timeDiv.className = 'text-xs text-gray-500';
timeDiv.textContent = message.created_at;
messageDiv.appendChild(nameDiv);
messageDiv.appendChild(contentDiv);
messageDiv.appendChild(timeDiv);
chatMessages.appendChild(messageDiv);
scrollToBottom();
}
// フォーム送信
chatForm.addEventListener('submit', function(e) {
e.preventDefault();
const message = messageInput.value;
if (!message.trim()) return;
// メッセージをサーバーに送信
fetch('/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value
},
body: JSON.stringify({ message })
})
.then(response => response.json())
.then(data => {
// 自分のメッセージを表示
addMessage({
username: '{{ auth()->user()->name }}',
message: message,
created_at: new Date().toISOString().replace('T', ' ').substring(0, 19)
}, true);
// 入力フィールドをクリア
messageInput.value = '';
})
.catch(error => console.error('Error:', error));
});
// WebSocketsでメッセージを受信
window.Echo.channel('chat')
.listen('NewMessage', (e) => {
// 他のユーザーからのメッセージを表示
addMessage(e);
});
});
</script>
@endpush
</x-app-layout>
WebSocketサーバーの起動
WebSocketサーバーを起動します:
php artisan websockets:serve
別のターミナルでLaravelの開発サーバーも起動します:
php artisan serve
アプリケーションの使用
- ブラウザで
http://localhost:8000/register
にアクセスして、アカウントを作成します - ログイン後、
http://localhost:8000/chat
にアクセスしてチャットを開始します - 複数のブラウザやシークレットウィンドウで異なるアカウントでログインし、リアルタイムでメッセージのやり取りを確認できます
WebSocketsダッシュボード
Laravel WebSocketsにはダッシュボードが付属しています。config/websockets.php
で'dashboard' => ['enable' => true]
を確認し、http://localhost:8000/laravel-websockets
にアクセスすると、接続状況などを確認できます。
セキュリティ対策
実際の環境では、以下のセキュリティ対策を検討しましょう:
- WebSocketsサーバーへのアクセス制限
- SSL/TLS暗号化の設定
- メッセージのバリデーションとサニタイズ
- 認証済みユーザーのみがメッセージを送信できるように制限
機能拡張のアイデア
このベースアプリケーションから、以下のような機能を追加することができます:
- プライベートチャットルーム
- 既読確認機能
- ファイル添付機能
- メンション機能
- 絵文字サポート
まとめ
Laravel WebSocketsを使用すると、比較的簡単にリアルタイムチャットアプリケーションを構築できます。このパッケージは開発環境だけでなく本番環境でも利用可能で、Pusherサービスの代わりとして使用できるため、コスト効率も良いでしょう。
リアルタイム機能は現代のWebアプリケーションにおいて重要な要素となっており、Laravel WebSocketsは手軽にその機能を実装できるツールとして非常に便利です。ぜひ自分のアプリケーションに取り入れてみてください。
コメント