Tuesday, 25 February 2025

Building a Scalable & Secure Private Chat with Laravel 11, Reverb, Redis & Vue 3 + TypeScript

This guide walks through creating a production-ready real-time private chat system using:
Laravel 11 (Backend)
Reverb + Redis (Real-time WebSockets)
Vue 3 + TypeScript (Frontend)
Nginx Load Balancer (Multi-server Scaling)


🔹 Backend: Laravel 11 + Reverb + Redis

1️⃣ Setup Laravel 11 & Dependencies

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

2️⃣ Install & Configure Laravel Reverb

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

🔹 Modify .env

env
BROADCAST_CONNECTION=redis QUEUE_CONNECTION=redis

🔹 Modify config/broadcasting.php

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

🔹 Start Redis

bash
redis-server

🛠 Step 2: Database & Models

1️⃣ Create Chat Model & Migration

bash
php artisan make:model Chat -m

Define chats table structure

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'); // Store encrypted messages $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'); } };

Run Migration

bash
php artisan migrate

🛠 Step 3: Secure Private Channels

1️⃣ Define Private Channel Authorization

Edit routes/channels.php:

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

🛠 Step 4: Broadcast Events Securely

1️⃣ Create Chat Event

bash
php artisan make:event MessageSent

Modify MessageSent.php

php
namespace App\Events; use App\Models\Chat; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PrivateChannel; 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(), ]; } }

🛠 Step 5: Send Messages

1️⃣ Create ChatController.php

bash
php artisan make:controller ChatController

2️⃣ Store & Broadcast Messages Securely

Modify 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', ]); // Encrypt message before storing $chat = Chat::create([ 'sender_id' => Auth::id(), 'receiver_id' => $request->receiver_id, 'message' => encrypt($request->message), ]); // Broadcast event broadcast(new MessageSent($chat))->toOthers(); return response()->json(['message' => 'Sent successfully']); } }

🔹 Frontend: Vue 3 + TypeScript

🛠 Step 1: Setup Vue 3 + TypeScript

bash
npm create vue@latest chat-app-frontend cd chat-app-frontend npm install npm install @vueuse/core axios pusher-js laravel-echo

🛠 Step 2: Configure Laravel Echo

Modify src/plugins/echo.ts:

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

🔹 Modify .env

ini
VITE_APP_WS_HOST=localhost

🛠 Step 3: Chat Component

Create src/components/Chat.vue:

vue
<script setup lang="ts"> import { ref, onMounted } from 'vue'; import axios from 'axios'; import echo from '@/plugins/echo'; const userId = 1; // Replace with auth user ID const messages = ref<{ sender_id: number; message: string }[]>([]); const newMessage = ref(''); const sendMessage = async () => { await axios.post('http://localhost:8000/api/send-message', { receiver_id: 2, // Change to actual receiver message: newMessage.value }); newMessage.value = ''; }; onMounted(() => { echo.private(`chat.${userId}`) .listen('MessageSent', (e: any) => { messages.value.push(e); }); }); </script> <template> <div> <div v-for="msg in messages" :key="msg.message"> <strong v-if="msg.sender_id === userId">You:</strong> <strong v-else>Friend:</strong> {{ msg.message }} </div> <input v-model="newMessage" @keyup.enter="sendMessage" placeholder="Type a message..." /> <button @click="sendMessage">Send</button> </div> </template>

🔹 Deployment: Multi-Server Scaling

🛠 Step 1: Configure Load Balancer

Modify Nginx config:

nginx
upstream websocket_servers { server 192.168.1.101:6001; server 192.168.1.102:6001; } server { listen 80; server_name example.com; location /reverb { proxy_pass http://websocket_servers; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $host; } }

🚀 Summary

Private Chat with Laravel 11 + Reverb + Redis
Vue 3 + TypeScript Frontend
Private Channels for Authentication
Load Balanced WebSockets for Multi-Server Scaling

💡 Next Step: Want a Docker setup for full production deployment? 🚀

No comments:

Post a Comment

Golang Advanced Interview Q&A