Tuesday, 25 February 2025

Building a private chat real-time system using Laravel 11, Reverb, and Redis.

Here’s a step-by-step guide to building a private chat real-time system using Laravel 11, Reverb, and Redis.


Step 1: Install Laravel 11

If you haven’t already installed Laravel 11, create a new project:

bash
composer create-project laravel/laravel chat-app cd chat-app

Then, install dependencies:

bash
composer require predis/predis

Step 2: Install & Configure Laravel Reverb

1️⃣ Install Reverb

bash
php artisan reverb:install

2️⃣ Publish Reverb config file

bash
php artisan vendor:publish --tag=reverb-config

3️⃣ Update .env to use Redis broadcasting

env
BROADCAST_CONNECTION=redis QUEUE_CONNECTION=redis

4️⃣ Update config/broadcasting.php to use Redis

php
'connections' => [ 'redis' => [ 'driver' => 'redis', 'connection' => 'default', ], ],

5️⃣ Start Redis

bash
redis-server

Step 3: Create WebSocket Channel for Private Chat

1️⃣ Define a private channel in routes/channels.php

php
use Illuminate\Support\Facades\Broadcast; Broadcast::channel('chat.{receiverId}', function ($user, $receiverId) { return (int) $user->id === (int) $receiverId; });

💡 This ensures only authenticated users can listen to their own chat events.


Step 4: Create a Chat Model & Migration

1️⃣ Generate Chat Model & Migration

bash
php artisan make:model Chat -m

2️⃣ Define the Schema in database/migrations/YYYY_MM_DD_create_chats_table.php

php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up() { Schema::create('chats', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('sender_id'); $table->unsignedBigInteger('receiver_id'); $table->text('message'); $table->timestamps(); $table->foreign('sender_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('receiver_id')->references('id')->on('users')->onDelete('cascade'); }); } public function down() { Schema::dropIfExists('chats'); } };

3️⃣ Run Migration

bash
php artisan migrate

Step 5: Create Chat Event

1️⃣ Generate an Event

bash
php artisan make:event MessageSent

2️⃣ Edit app/Events/MessageSent.php

php
namespace App\Events; use App\Models\Chat; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use Illuminate\Queue\SerializesModels; class MessageSent implements ShouldBroadcastNow { use InteractsWithSockets, SerializesModels; public $chat; public function __construct(Chat $chat) { $this->chat = $chat; } public function broadcastOn() { return new PrivateChannel('chat.' . $this->chat->receiver_id); } public function broadcastWith() { return [ 'message' => $this->chat->message, 'sender_id' => $this->chat->sender_id, 'receiver_id' => $this->chat->receiver_id, 'timestamp' => $this->chat->created_at->toDateTimeString(), ]; } }

💡 This event will broadcast new messages to the receiver’s private channel.


Step 6: Create Chat Controller

1️⃣ Generate a Chat Controller

bash
php artisan make:controller ChatController

2️⃣ Edit app/Http/Controllers/ChatController.php

php
namespace App\Http\Controllers; use App\Events\MessageSent; use App\Models\Chat; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class ChatController extends Controller { public function sendMessage(Request $request) { $request->validate([ 'receiver_id' => 'required|exists:users,id', 'message' => 'required|string', ]); $chat = Chat::create([ 'sender_id' => Auth::id(), 'receiver_id' => $request->receiver_id, 'message' => $request->message, ]); broadcast(new MessageSent($chat))->toOthers(); return response()->json(['message' => 'Sent successfully']); } }

💡 This method stores the message and broadcasts it to the receiver.


Step 7: Define API Route

Edit routes/api.php:

php
use App\Http\Controllers\ChatController; Route::middleware('auth:sanctum')->post('/send-message', [ChatController::class, 'sendMessage']);

💡 Ensure your API uses auth:sanctum or another authentication method.


Step 8: Start Reverb WebSocket Server

1️⃣ Run Laravel Queues (Redis required)

bash
php artisan queue:work

2️⃣ Start Laravel Reverb

bash
php artisan reverb:start

Step 9: Frontend Integration (Example in JavaScript)

Here’s how to listen for messages using Laravel Echo:

1️⃣ Install Laravel Echo & Pusher JS

bash
npm install laravel-echo pusher-js

2️⃣ Configure Laravel Echo in resources/js/bootstrap.js

js
import Echo from 'laravel-echo'; import Pusher from 'pusher-js'; window.Pusher = Pusher; window.Echo = new Echo({ broadcaster: 'pusher', key: 'reverb', wsHost: window.location.hostname, wsPort: 6001, wssPort: 6001, forceTLS: false, disableStats: true, enabledTransports: ['ws', 'wss'] });

3️⃣ Listen for messages (Example in Vue.js or React)

js
window.Echo.private(`chat.${userId}`) .listen('MessageSent', (e) => { console.log("New message received:", e.message); });

Step 10: Test Everything

1️⃣ Start Laravel Backend

bash
php artisan serve

2️⃣ Start Reverb

bash
php artisan reverb:start

3️⃣ Send a Message using Postman or Frontend

json
POST /api/send-message { "receiver_id": 2, "message": "Hello, how are you?" }

4️⃣ Check if the receiver gets the real-time message!


How to Scale Laravel Reverb for Multi-Server?

  1. Ensure Redis is used for broadcasting (BROADCAST_CONNECTION=redis).
  2. Deploy Laravel on multiple servers.
  3. Run php artisan reverb:start on each server.
  4. Use a Load Balancer (e.g., Nginx, AWS ALB) to route WebSocket traffic.
  5. Enable Redis clustering for large-scale chat applications.

Summary

Laravel Reverb provides a native WebSocket solution for Laravel 11.
Redis is used to broadcast events across multiple servers.
Private Channels ensure secure messaging.
Frontend listens for messages using Laravel Echo.
For multi-server support, use Redis + Load Balancer.

Would you like a Docker setup for a production-ready chat app? 🚀


Xác thực Private Chat trong Laravel Reverb

Để đảm bảo chỉ người dùng hợp lệ mới có thể lắng nghe và nhận tin nhắn trong Private Chat, Laravel sử dụng Private Channels kết hợp với cơ chế xác thực.


1. Xác thực Private Channel

Trong routes/channels.php:

php
use Illuminate\Support\Facades\Broadcast; Broadcast::channel('chat.{receiverId}', function ($user, $receiverId) { return (int) $user->id === (int) $receiverId; });

🔹 Giải thích:

  • Khi một người dùng cố gắng lắng nghe kênh chat.{receiverId}, Laravel sẽ gọi callback xác thực.
  • Chỉ khi $user->id === $receiverId, Laravel mới cho phép người dùng lắng nghe kênh.
  • Điều này đảm bảo chỉ người nhận tin nhắn mới có thể nghe tin nhắn đến.

2. Xác thực Người Dùng Trước Khi Gửi Tin Nhắn

Trong ChatController.php, Laravel bắt buộc user phải đăng nhập để gửi tin nhắn:

php
public function sendMessage(Request $request) { $request->validate([ 'receiver_id' => 'required|exists:users,id', 'message' => 'required|string', ]); $chat = Chat::create([ 'sender_id' => Auth::id(), 'receiver_id' => $request->receiver_id, 'message' => $request->message, ]); broadcast(new MessageSent($chat))->toOthers(); return response()->json(['message' => 'Sent successfully']); }

🔹 Giải thích:

  • Auth::id() lấy ID của user hiện tại (bắt buộc phải login).
  • Chặn gửi tin nhắn đến user không tồn tại với exists:users,id.
  • Dữ liệu chỉ được lưu & phát đi nếu user hợp lệ.

3. Xác thực Khi Lắng Nghe Kênh Trên Frontend

Laravel Echo sẽ gửi request xác thực trước khi lắng nghe Private Channel.

Ví dụ, trong Vue.js/React/JavaScript:

js
window.Echo.private(`chat.${userId}`) .listen('MessageSent', (e) => { console.log("Tin nhắn mới:", e.message); });

📌 Lưu ý:

  • Laravel sẽ tự động chặn kết nối nếu user không được phép nghe kênh chat.{receiverId}.
  • Nếu user chưa đăng nhập, Laravel sẽ từ chối kết nối.

4. Laravel Xác Thực Private Channel Như Thế Nào?

Khi frontend yêu cầu lắng nghe một Private Channel, Laravel sẽ:

  1. Kiểm tra user đã đăng nhập hay chưa.
  2. Chạy callback trong routes/channels.php để xem user có quyền nghe không.
  3. Trả về lỗi nếu không hợp lệ, hoặc cho phép kết nối nếu hợp lệ.

📌 Tổng Kết

Private Channel (chat.{receiverId}) đảm bảo chỉ người nhận mới được nghe tin nhắn.
Laravel Echo gửi yêu cầu xác thực trước khi cho phép kết nối.
Backend yêu cầu user phải đăng nhập khi gửi tin nhắn (Auth::id()).
Nếu không xác thực, user không thể nhận tin nhắn private.

🔹 Với cách này, tin nhắn của user sẽ an toàn và không bị nghe lén. 🚀

No comments:

Post a Comment

Golang Advanced Interview Q&A