Real-Time Chat App with Laravel Websockets

Real-Time Chat App with Laravel Websockets

Introduction 

You'll gain insights into the significance of real-time communication by creating a chat app in laravel using laravel websockets and pusher.We will deep dive into it and guide each and everything in detail.We will start form beginner and go advance, so this tutorial is for both for beginner and advance.

We will have 4 section for this tutorial.

Section 1: Sending Messages via Websockets and Broadcasting Events

Section 2: Receiving Messages using Pusher

Section 3: Displaying User Status (Online/Offline)

Section 4: Displaying Old Messages of Every User

Section 1: Sending Messages via Websockets and Broadcasting Events

In this section, we'll talk about  the process of sending messages through Websockets in Laravel. You'll learn how to set up Laravel Websockets, create event listeners, and broadcast events to clients in real-time.

Setting Up Laravel

  • Start by creating a fresh project.
  • Use your .env and connect db
  • We will also use default users table 

We need to create a table  where we store chats .Let’s create it.

php artisan make:model Chat -m

Our chats table.

Schema::create('chats', function (Blueprint $table) { $table->id(); $table->foreignId('sender_id')->constrained('users')->cascadeOnDelete(); $table->foreignId('receiver_id')->constrained('users')->cascadeOnDelete(); $table->string('message'); $table->timestamps(); });

Our Chat Model

class Chat extends Model { use HasFactory; protected $fillable = [ 'sender_id', 'receiver_id', 'message' ]; }

Configure Laravel Websockets

Install it by using composer

composer require beyondcode/laravel-websockets

If you want to see statistics and information you can publish a migration file.

php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"

Now migrate it

php artisan migrate

 Now publish configuration

php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"
Now inside config/websockets.php
'apps' => [ [ 'id' => env('PUSHER_APP_ID'), 'name' => env('APP_NAME'), 'key' => env('PUSHER_APP_KEY'), 'secret' => env('PUSHER_APP_SECRET'), 'path' => env('PUSHER_APP_PATH'), 'capacity' => null, 'enable_client_messages' => false, 'enable_statistics' => true, ], ],

.env file

PUSHER_APP_ID=local PUSHER_APP_KEY=local PUSHER_APP_SECRET=local PUSHER_HOST=127.0.0.1 PUSHER_PORT=6001 PUSHER_SCHEME=https PUSHER_APP_CLUSTER=mt1

User Authentication & User Interface

For authentication we will use breeze , you can install any package or make custom authentication.

composer require laravel/breeze --dev php artisan breeze:install php artisan migrate npm install npm run dev

Register some user so that we can show a list of users in our chat app.

Create a controller 

php artisan make:Controller MessageController

Here we define our complete logic.Navigate it to this File

public function index(Request $request) { $users = User::whereNotIn('id', [auth()->user()->id])->get(); return view("dashboard", compact("users")); }

Here's our route

// Route::get('/dashboard', function () { // return view('dashboard'); // })->middleware(['auth', 'verified'])->name('dashboard'); Route::get('/dashboard', [MessageController::class, 'index'])->middleware(['auth', 'verified'])->name('dashboard');

We need to add out frontend chatapp code and show a list of users So Navigate to views\dashboard.blade.php

<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ "Dashboard" }} </h2> </x-slot> <div class="py-12"> <div id="hello-message"> <!-- The received hello message will be displayed here --> </div> <div class="container mx-auto shadow-lg rounded-lg"> <!-- headaer --> <div class="px-5 py-5 flex justify-between items-center bg-white border-b-2"> <div class="font-semibold text-2xl">Laravel ChatApp</div> <div class="h-12 w-12 p-2 bg-yellow-500 rounded-full text-white font-semibold flex items-center justify-center"> RA </div> </div> <!-- end header --> <!-- Chatting --> <div class="flex flex-row justify-between bg-white" style="height: 60vh;"> <!-- chat list --> <div class="flex flex-col w-2/5 border-r-2 overflow-y-auto"> <!-- search compt --> <div class="border-b-2 py-4 px-2"> <input type="text" placeholder="search chatting" class="py-2 px-2 border-2 border-gray-200 rounded-2xl w-full" /> </div> <!-- user list --> @foreach ($users as $user) <div class="user_list flex flex-row py-4 px-2 justify-center items-center border-b-2" data-id="{{ $user->id }}"> <div class="w-1/4"> <img src="https://source.unsplash.com/_7LbC5J-jw4/600x600" class="object-cover h-12 w-12 rounded-full" alt="" /> </div> <div class="w-full"> <div class="text-lg font-semibold">{{ $user->name }}</div> <span class="text-gray-500 lastest__message"> </span> <span class="text-white bg-sky-300 rounded-full px-2 py-1" id="{{ $user->id }}-status">offline</span> </div> </div> @endforeach <!-- end user list --> </div> <!-- end chat list --> <!-- message --> <div class="w-full flex flex-col justify-between chatbox" style="display: none;"> <div class="px-5 overflow-y-auto chat-overflow"> <div class="flex flex-col mt-5 chat-div"> <!-- <div class="flex justify-end mb-4"> <div id="text-message" class="mr-2 py-3 px-4 bg-blue-400 rounded-bl-3xl rounded-tl-3xl rounded-tr-xl text-white"> Welcome to group everyone !</div> <img src="https://source.unsplash.com/vpOeXr5wmR4/600x600" class="object-cover h-8 w-8 rounded-full" alt="" /> </div> <div class="flex justify-start mb-4"> <img src="https://source.unsplash.com/vpOeXr5wmR4/600x600" class="object-cover h-8 w-8 rounded-full" alt="" /> <div class="ml-2 py-3 px-4 bg-gray-400 rounded-br-3xl rounded-tr-3xl rounded-tl-xl text-white"> Lorem ipsum dolor sit amet consectetur adipisicing elit. Quaerat at praesentium, aut ullam delectus odio error sit rem. Architecto nulla doloribus laborum illo rem enim dolor odio saepe, consequatur quas?</div> </div> --> </div> </div> <div class="py-5"> <form id="message-form" class="flex"> <input class="bg-gray-300 w-5/6 py-5 px-3" type="text" name="message" id="message" placeholder="type your message here..." /> <button class="w-1/6 bg-sky-400" id="submit_btn">Send</button> </form> </div> </div> </div> </div> </div> </div> </x-app-layout>
Chat app in Laravel

Send Message

Define sender_id and receiver_id globally.

var sender_id = @json(auth()->user()->id); var receiver_id;

Now let's send a message using ajax request , we will create an event and broadcast the message.

// JQuery $(document).ready(function() { //Users List $('.user_list').click(function() { var getUserId = $(this).attr('data-id'); receiver_id = getUserId; console.log(receiver_id); $('.user_list').removeClass('active'); $(this).addClass('active'); $('.chatbox').show(); loadOldChats(); scrollChat(); }); // Send Message Through Ajax $('#submit_btn').on('click', function(event) { event.preventDefault(); const formData = new FormData(document.getElementById("message-form")); formData.append('receiver_id', receiver_id); formData.append('sender_id', sender_id); $.ajax({ url: "{{ route('message.save') }}", data: formData, type: 'POST', headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') }, processData: false, contentType: false, cache: false, }).then(function(response) { console.log(response); $('#message').val(''); loadOldChats(); scrollChat(); }) .fail(function(response) { console.log(response); }); }); });

Our Route for this request

Route::post('/save-chat', [MessageController::class, 'sendMessage'])->name('message.save');

 

No go inside and MessageControllercreate **sendMessage** function

public function sendMessage(Request $request) { $request->validate([ 'receiver_id' => 'required', 'sender_id' => 'required', 'message' => 'required|string', ]); try { $chat = Chat::create([ 'receiver_id' => $request->receiver_id, 'sender_id' => $request->sender_id, 'message' => $request->message, ]); broadcast(new MessageSent($chat)); return response()->json([ "success" => true, "data" => $chat, ]); } catch (\Exception $e) { return response()->json([ "success" => false, "msg" => $e->getMessage() ]); } }

We need to create Event , which we will broadcast.

php artisan make:event MessageSent

Now go inside App\Events\MessageSent

namespace App\Events; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class MessageSent implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; /** * Create a new event instance. */ public $chat; public function __construct($chat) { $this->chat = $chat; } /** * Get the channels the event should broadcast on. * * @return array */ public function broadcastOn(): Channel { return new PrivateChannel('messageChannel'); } public function broadcastWith() { return [ 'chat' => $this->chat ]; } public function broadcastAs() { return 'getChatMessage'; } }

Now we need to define route for this event that will check if the user is authenticated or not. So navigate it to **routes\channels.php**

Broadcast::channel('messageChannel', function ($user) { return $user; });

Section 2: Receiving Messages using Pusher

We'll explore how to receive messages using Pusher, a powerful real-time messaging service.

composer require pusher/pusher-php-server

Goto your resources\js\bootstrap.js

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, cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1', wsHost: window.location.hostname, wsPort: 6001, forceTLS: false, disableStats: true, });

Now let's listen for and event. You can use your custom js file or you can use blade file to add js.

//Listen for an Event window.Echo.private('messageChannel').listen('.getChatMessage', (e) => { let ChatMessage = e.chat.message; let senderId = e.chat.sender_id; let recevierId = e.chat.receiver_id; if (sender_id == recevierId && receiver_id == senderId) { $('.chat-div').append(`
${ChatMessage}
`); scrollChat(); } });

Section 3:Displaying User Status (Online/Offline)

We'll focus on enhancing the user experience by displaying user status indicators, indicating whether they are online or offline.We need to create another event for this but this we will use presence channel instead of public to show user online/offline status.
php artisan make:event UserChatStatus
Navigate to this event
public function broadcastOn(): array { return [ new PresenceChannel('userChatStatusUpdate'), ]; }
Now define a event broadcasting channel (routes\channels.php)
Broadcast::channel('userChatStatusUpdate', function ($user) { return $user; });

Here we are listening for an userChatStatusUpdate Event and updating the status at real-time.

//Presence Channel (Check User is Online or Offline) window.Echo.join('userChatStatusUpdate') .here((users) => { console.log(users); for (let i = 0; i < users.length; i++) { if (sender_id != users[i]['id']) { $('#' + users[i]['id'] + '-status').removeClass('bg-sky-300'); $('#' + users[i]['id'] + '-status').addClass('bg-lime-600'); $('#' + users[i]['id'] + '-status').text('Online'); } } }) .joining((user) => { console.log(user.name); $('#' + user.id + '-status').removeClass('bg-sky-300'); $('#' + user.id + '-status').addClass('bg-lime-600'); $('#' + user.id + '-status').text('Online'); }) .leaving((user) => { console.log(user.name); $('#' + user.id + '-status').removeClass('bg-lime-600'); $('#' + user.id + '-status').addClass('bg-sky-300'); $('#' + user.id + '-status').text('Offline'); }) .error((error) => { console.error(error); });

Section 4 : Show old Message of Every User

Navigate to your MessageController and create a method named show().Which will retrieve all the users messages.
public function show(Request $request) { try { $chat = Chat::where(function ($q) use ($request) { $q->where('sender_id', '=', $request->receiver_id) ->orwhere('sender_id', '=', $request->sender_id); })->where(function ($q) use ($request) { $q->where('receiver_id', '=', $request->receiver_id) ->orwhere('receiver_id', '=', $request->sender_id); })->get(); return response()->json([ "success" => true, "data" => $chat, ]); } catch (\Exception $e) { return response()->json([ "success" => false, "msg" => $e->getMessage() ]); } }

Here we are getting all the messages and fixing scroll issue so that we see latest message everytime.

// JQuery $(document).ready(function() { scrollChat(); // Scroll function scrollChat() { $('.chat-overflow').animate({ scrollTop: $('.chat-overflow').offset().top + $('.chat-overflow')[0].scrollHeight }, 0); } // Showing OldChats function loadOldChats() { $.ajax({ url: '{{ route('message.old.show') }}', type: 'POST', headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') }, data: { sender_id: sender_id, receiver_id: receiver_id, }, success: function(response) { $('.chat-div').empty(); if (response.success) { let chats = response.data; if (response.success) { for (let i = 0; i < chats.length; i++) { $('.chat-div').append(`
${chats[i].message}
`); } } } scrollChat(); }, error: function(xhr, textStatus, errorThrown) { console.error(xhr.responseText); } }); } });
Our All Routes of web.php
Route::get('/', function () { return view('welcome'); }); Route::get('/dashboard', [MessageController::class, 'index'])->middleware(['auth', 'verified'])->name('dashboard'); Route::middleware('auth')->group(function () { Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit') Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); Route::post('/save-chat', [MessageController::class, 'sendMessage'])->name('message.save'); Route::post('/show', [MessageController::class, 'show'])->name('message.old.show'); }); require __DIR__ . '/auth.php';
Tags
Laravelwebsockets Realtimecommunication Chatapplication Websocketsinlaravel Userpresence Realtimeupdates Onlinestatus Chatappinlaravel Dynamicchatapp Realtimechat Websocketsexplained Instantmessaging