Your Guide to Laravel Excellence

Real-Time Chat App with Laravel Websockets

Real-Time Chat App with Laravel Websockets

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

  • 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

inside MessageController

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', [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>

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;

    public $chat;

    public function __construct($chat)
    {
        $this->chat = $chat;
    }

    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';

Recommeded Posts

Laravel custom login and register Tutorial

Laravel custom login and register Tutorial

Laravel custom login and register Tutorial

1 month ago Read article →
How to Restrict php artisan migrate:fresh on a Laravel Production Server

How to Restrict php artisan migrate:fresh on a Laravel Production Server

how to safely restrict php artisan migrate:fresh and other destructive commands from running on your Laravel production server.

1 month ago Read article →
How to Prevent Spam in Laravel Forms with spatie/laravel-honeypot

How to Prevent Spam in Laravel Forms with spatie/laravel-honeypot

How to Prevent Spam in Laravel Forms with spatie/laravel-honeypot

1 month ago Read article →
How to Implement Custom Facebook OAuth Login in Laravel  Without Socialite

How to Implement Custom Facebook OAuth Login in Laravel Without Socialite

How to Implement Custom Facebook OAuth Login in Laravel Without Socialite

1 month ago Read article →