
Build a Real-Time Chat App with Laravel 11, Livewire 3 & Bootstrap 5
In today’s fast-paced world, real-time communication is the heartbeat of modern applications. From social messaging platforms to customer support systems, live chat is everywhere—and users expect instant responsiveness.
In this tutorial, we'll walk through building a simple yet functional chat app that allows two users to exchange messages dynamically.
🛠️ Tools & Tech Stack
- Laravel 11
- Laravel Jetstream (Livewire or Inertia)
- Livewire 3
- Bootstrap 5
- MySQL
- Laravel Breeze (alternative to Jetstream)
✅ Step 1: Install Laravel Jetstream
Jetstream provides full-featured auth scaffolding: registration, login, email verification, two-factor authentication, profile management, and more.
🔧 Installation Steps:
composer require laravel/jetstream
Now, choose your stack:
For Livewire (recommended for Blade apps):
php artisan jetstream:install livewire
Then compile assets and run migrations:
npm install && npm run dev php artisan migrate
📁 Jetstream Folder Structure Overview
- resources/views/auth/ – Auth views (login, register, etc.)
- resources/views/layouts/app.blade.php – App layout
- resources/views/profile/ – Profile pages
- resources/views/dashboard.blade.php – Dashboard
- app/Actions/Fortify/ – Fortify actions
- app/Actions/Jetstream/ – Jetstream team management
- routes/web.php – Web routes
- routes/auth.php – Auth routes
🧱 Step 2: Create the Messages Table
php artisan make:migration create_messages_table
Update your migration file:
Schema::create('messages', function (Blueprint $table) { $table->id(); $table->foreignId('sender_id')->constrained('users')->onDelete('cascade'); $table->foreignId('receiver_id')->constrained('users')->onDelete('cascade'); $table->text('message'); $table->boolean('is_read')->default(false); $table->timestamps(); });
Then run:
php artisan migrate
🧩 Step 3: Update the Layout with Bootstrap & Livewire
Update resources/views/layouts/app.blade.php:
<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>{{ config('app.name', 'Laravel') }}</title> <meta name="csrf-token" content="{{ csrf_token() }}"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> @livewireStyles </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4"> <div class="container"> <a class="navbar-brand" href="{{ url('/') }}">{{ config('app.name', 'Laravel') }}</a> <div class="collapse navbar-collapse"> <ul class="navbar-nav ms-auto"> @auth <li class="nav-item"><span class="nav-link">Hi, {{ Auth::user()->name }}</span></li> <li class="nav-item"> <form method="POST" action="{{ route('logout') }}"> @csrf <button class="btn btn-link nav-link" type="submit">Logout</button> </form> </li> @else <li class="nav-item"><a class="nav-link" href="{{ route('login') }}">Login</a></li> <li class="nav-item"><a class="nav-link" href="{{ route('register') }}">Register</a></li> @endauth </ul> </div> </div> </nav> @isset($header) <header class="container mb-4"><div class="row"><div class="col">{{ $header }}</div></div></header> @endisset <main class="container">{{ $slot }}</main> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> @livewireScripts </body> </html>
💬 Step 4: Create the Message Model
Create Message.php:
namespace App\Models; use Illuminate\Database\Eloquent\Model; class Message extends Model { protected $fillable = ['sender_id', 'receiver_id', 'message']; }
⚡ Step 5: Build the Livewire Chat Component
php artisan make:livewire Chat
Update app/Livewire/Chat.php:
namespace App\Livewire; use Livewire\Component; use App\Models\Message; use Illuminate\Support\Facades\Auth; class Chat extends Component { public $receiverId; public $message; public $messages = []; protected $rules = ['message' => 'required|string']; public function mount($receiverId) { $this->receiverId = $receiverId; $this->loadMessages(); } public function loadMessages() { $this->messages = Message::where(function ($q) { $q->where('sender_id', Auth::id())->where('receiver_id', $this->receiverId); })->orWhere(function ($q) { $q->where('sender_id', $this->receiverId)->where('receiver_id', Auth::id()); })->orderBy('created_at')->get()->toArray(); } public function sendMessage() { $this->validate(); Message::create([ 'sender_id' => Auth::id(), 'receiver_id' => $this->receiverId, 'message' => $this->message ]); $this->message = ''; $this->loadMessages(); } public function render() { return view('livewire.chat'); } }
🖼️ Step 6: Create the Chat UI
resources/views/livewire/chat.blade.php
<div class="container mt-4"> <div class="card"> <div class="card-header bg-primary text-white"> <h5 class="mb-0">Chat</h5> </div> <div class="card-body" style="height: 300px; overflow-y: auto;" wire:poll.2s="loadMessages"> @foreach ($messages as $msg) <div class="mb-2 d-flex {{ $msg['sender_id'] == auth()->id() ? 'justify-content-end' : 'justify-content-start' }}"> <span class="badge p-2 {{ $msg['sender_id'] == auth()->id() ? 'bg-primary' : 'bg-secondary' }}"> {{ $msg['message'] }} </span> </div> @endforeach </div> <div class="card-footer"> <div class="input-group"> <input type="text" wire:model="message" class="form-control" placeholder="Type a message..." required> <button wire:click="sendMessage" class="btn btn-primary">Send</button> </div> </div> </div> </div>
🌐 Step 7: Add Route & Chat Controller
web.php:
use App\Http\Controllers\ChatController; Route::get('/chat/{receiverId}', [ChatController::class, 'chat'])->middleware('auth');
ChatController.php:
namespace App\Http\Controllers; use Illuminate\Http\Request; class ChatController extends Controller { public function chat() { return view('chat'); } }
resources/views/chat.blade.php
<x-app-layout> <livewire:chat :receiverId="request()->route('receiverId')" /> </x-app-layout>
✅ Done! Time to Chat
- Log in as User A
- Visit /chat/{userB_id}
- http://127.0.0.1:8000/chat/{userB_id}
- Start chatting 💬
Livewire’s wire:poll.2s gives your app a real-time-like experience, updating the messages every 2 seconds.
💡 Final Thoughts
You've just built a basic real-time chat app with Laravel 11 and Livewire 3—without writing a single line of JavaScript! 🚫💻
To take this even further, consider:
- Laravel Echo + Pusher for true WebSocket-powered real-time chat
- Typing indicators, message read receipts, and media attachments
- Group chats or channel-based messaging.
0 Comments