Laravel WebSockets Chat | Chat App 03

Laravel WebSockets Chat

Let’s add real-time updates and notifications to our chat app using Laravel WebSockets, the Pusher-free alternative.

This is the part you are waiting for, I feel ya, my friend. It’s the real-time chat app moment, Let’s make it happen.

Course content

Building Laravel & Vue.js Chat App 00 | Introduction
Laravel Login And Register RESTful API | Chat app 01
Laravel Chat RESTful API | Chat App 02 
Laravel WebSockets Chat | Chat App 03
Vue.js Chat App Frontend | Chat App 04
Vue 3 With Laravel Echo WebSocket | Chat App 05

How does Real-time chat work?

Let’s say chat room means WebSocket channel, to enter the room, we need to subscribe to that channel first. After subscription, We are ready to receive any message from the server to the Frontend, without any HTTP or AJAX Requests.

The sender will send the message via HTTP request to the server so we can save the message in the database then the server will broadcast the message to the chat channel to other users so they will receive the message. It’s so simple like that.

Laravel WebSockets

As most Laravel real-time tutorials trick you to use Pusher, Here I recommend using Laravel WebSockets for free with no limitations.

We can install Laravel-websockets package via composer and run the following commands

Note, If you want to learn more here is a tutorial: How To Use Laravel WebSockets And Laravel Echo With Vue 3 App Example.

composer require beyondcode/laravel-websockets
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"
php artisan migrate
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"
composer require pusher/pusher-php-server "~3.0"

We need to make our BROADCAST_DRIVER to be pusher in .env file.

BROADCAST_DRIVER=pusher

Below is the pusher configuration that was modified for Laravel websockets in config/broadcasting.php. This data is for Localhost and the default port of the Laravel WebSocket server is 6001.

'pusher' => [
    'driver' => 'pusher',
    'key' => env('PUSHER_APP_KEY'),
    'secret' => env('PUSHER_APP_SECRET'),
    'app_id' => env('PUSHER_APP_ID'),
    'options' => [
        'cluster' => env('PUSHER_APP_CLUSTER'),
        'encrypted' => true,
        'host' => '127.0.0.1',
        'port' => 6001,
        'scheme' => 'http'
    ],
],

In .env file make sure to set your pusher app id, key, secret, and cluster.

PUSHER_APP_ID=local
PUSHER_APP_KEY=local
PUSHER_APP_SECRET=local
PUSHER_APP_CLUSTER=mt1

Now we are ready to test our WebSockets server.

php artisan websockets:serve

Let’s run the Laravel server and open the Laravel WebSockets dashboard http://127.0.0.1:8000/laravel-websockets

php artisan serve

Add this route to routes\api.php

Broadcast::routes(['middleware' => ['auth:sanctum']]);

Uncomment App\Providers\BroadcastServiceProvider::class, in config\app.php at providers

Message Sent Event

Let’s create a Laravel event for sending messages so we can broadcast the message to the other users who subscribed to the same chat channel for real-time updates.

php artisan make:event ChatMessageSent
<?php

namespace App\Events;

use App\Models\ChatMessages;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Http\Resources\MassageResource;

class ChatMessageSent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;


    public $message;

    public function __construct(MassageResource $message)
    {
        $this->message = $message;
    }
    public function broadcastWith(){
        return ['message'=> $this->message];
    }
    /**
     * Get the channels the event should broadcast on.
     *
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new PresenceChannel('chat.'.$this->message->chat_id),
        ];
    }
}

That’s the event we will trigger, so let’s explain, $message this is the message that will be broadcasted on the Presence channel “chat.[chat_id]”.

Note, you can change the chat_id with a customized secret key to be more secure

To make sure that only these chat participants can subscribe to this channel so no one else can see the chat messages, Add the following routes in routes\channels.php

use App\Models\Chat;
//
Broadcast::channel('chat.{id}', function ($user, $id) {
    $chat = Chat::find($id);
    if($chat->isParticipant($user->id)){
        return ['id' => $user->id, 'name' => $user->first_name];
    }
});

Now our chat is secure, and let’s hope that the “Snowden” movie is not true.

Let’s trigger the event in sendTextMessage() function in ChatController

use App\Events\ChatMessageSent;

class ChatController extends Controller
{


public function sendTextMessage(SendTextMessageRequest $request){
        $chat = Chat::find($request->chat_id);
        if($chat->isParticipant($request->user()->id)){
        $message = ChatMessages::create([
            'message' => $request->message,
            'chat_id' => $request->chat_id,
            'user_id' => $request->user()->id,
            'data' => json_encode(['seenBy'=>[],'status'=>'sent']) //sent, delivered,seen
        ]);
        $success = true;
        $message =  new MassageResource($message);
       
      // broadcast the message to all users 
        broadcast(new ChatMessageSent($message));

        foreach($chat->participants as $participant){
            if($participant->id != $request->user()->id){
                $participant->notify(new NewMessage($message));
            }
        }
        
        return response()->json( [
            "message"=> $message,
            "success"=> $success
        ],200);
        }else{
        return response()->json([
            'message' => 'not found'
        ], 404);
        }
    }

//

Message Status Event

Let’s create another event when the user sees the message

php artisan make:event ChatMessageStatus
<?php

namespace App\Events;


use App\Models\ChatMessages;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Http\Resources\MassageResource;

class ChatMessageStatus implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     */

    public $message;

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

    public function broadcastWith(){
        return ['message'=> $this->message];
    }
    /**
     * Get the channels the event should broadcast on.
     *
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new PresenceChannel('chat.'.$this->message->chat_id),
        ];
    }
}

Nothing new, all the same as ChatMessageSent event, and we broadcast on the same channel.

public function messageStatus(Request $request,ChatMessages $message){
        if($message->chat->isParticipant($request->user()->id)){
            $messageData = json_decode($message->data);
            array_push($messageData->seenBy,$request->user()->id);
            $messageData->seenBy = array_unique($messageData->seenBy);
            if(count($message->chat->participants)-1 < count( $messageData->seenBy)){
                $messageData->status = 'delivered';
            }else{
                $messageData->status = 'seen';    
            }
            $message->data = json_encode($messageData);
            $message->save();
            $message =  new MassageResource($message);
            
            //triggering the event
            broadcast(new ChatMessageStatus($message));

            return response()->json([
                'message' =>  $message,
                'success' => true
            ], 200);
        }else{
            return response()->json([
                'message' => 'Not found',
                'success' => false
            ], 404); 
        }
    }

Congratulations, we have just finished our backend. Let’s create our frontend in the next tutorial Vue.js Chat App Frontend | Chat App 04