Saturday, 3 September 2022

Real-time Laravel Ratchet Websocket Chat Application


In this tutorial, we will discuss How to make Real Time Chat Application in Laravel 9 Framework by using Ratchet Web Socket Library. In this tutorial, we will build Real-time Chat Application in which we users can do real time conversation via this Chat Application. So if you are searching tutorial on Real-time Laravel Chat Application with Web Socket, then you have land on the right place because here, we will build Real-time Chat Application using Laravel and Ratchet Web Socket.

Under this tutorial, we will not use any paid messaging service but for send and receive chat message in real-time here we will use Ratchet Web Socket Library with Laravel 9 framework. So at backend we will use Laravel 9 framework, Ratchet Websocket Library and MySQL database and at front-end we will use Vanilla JavaScript and Bootstrap 5 Library. So this web technology we will use for build real time chat application in Laravel framework by using Ratchet Websocket Library. In the current time, in every web application that requires real time communication so for implement real time communication, here we have use Ratchet Websocket Library with Laravel framework and by using this Library we can implement real time communication of data and build chat application.

What is Ratchet WebSocket?


Ratchet WebSocket is PHP Library which will provides tools to developer for create real-time web application in which client and server are connected in bi-directional, so client directly send message to other client via this Web Socket. It will create tunnel between two or multiple client and they can sed and receive message in real-time. This Ratchet WebSocket will make persistent connection from browser to the server and once the connection has been established then that connection will be open until client or server decide to close it. So in open connection mode, the client send message to other client.

 

Web Technology used for Build Laravel Chat Application


Server Side


  • Laravel 9 Framework
  • MySQL Database
  • Ratchet WebSocket
  • Composer

Client Side


  • Vanilla JavaScript
  • Bootstrap 5 Library

Real-time Laravel Ratchet Websocket Chat Application

Architecture of Laravel Ratchet Web Socket Chat Application


In Below image you can find the basic architecture of Laravel Ratchet Web Socket Chat Application.


Real-time Laravel Ratchet Websocket Chat Application


Features of Laravel Ratchet WebSocket Chat Application


Below you can find different feature of Real-time Laravel Ratchet WebSocket Chat Application.

  1. Chat Application Registration
  2. Chat Application Login
  3. Manage Profile
  4. List Unconnected User with Send Chat Request Button
  5. Search User and List with Send Chat Request Button
  6. Send Chat Request to User
  7. Display User Chat Request in Notification Area
  8. Approve or Reject User Chat Request in Notification Area
  9. Display Approve Chat Request User in Connected User Area
  10. List Approve Chat Request User in Connected User Area
  11. Make Chat Area with Send Chat Button
  12. Reset Chat Area
  13. Load Chat history when User has select another user to Chat
  14. Send Chat Message to One to One User
  15. Whatsup like Message Send Read status using icon
  16. Display number of unread message notification
  17. Display User is Online or Not using icon
  18. Display Whatsup like User Last Seen Detail

Below you can find step by step process for build Laravel Ratchet WebSocket Chat Application.

  1. Install Laravel 9 Application
  2. Make Database Connection
  3. Create Model Class
  4. Download & Install Ratchet WebSocket Library
  5. Create Controller
  6. Create View Blades Files
  7. Set Route of Controller Method
  8. Run Laravel 9 Application

Step 1 - Install Laravel 9 Application


In first step, we want to download fresh copy of Laravel 9 framework. So we have goes to command prompt and goes to directory where we can run our PHP code. So after goes into that directory, we have to run following command which will make custom_login directory and under that directory it will download and install Laravel 9 Application.


composer create-project laravel/laravel custom_demo


Step 2 - Make Database Connection


Once we have download and install Laravel 9 Application, now we first we want to make MySQL database connection. So for this, we have to open .env file and under this file, we have to define MySQL database configuration. So after define this configuration details it will make MySQL database connection with Laravel 9 Application.

.env

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=testing
DB_USERNAME=root
DB_PASSWORD=


So after make MySQL Database connection, now we want to make user table under mysql database. So here we will use default user table of Laravel 9 Application. So here user table defination has been stored under database/migrations directory. So for create table in MySQL database from Laravel Application, we have to run following command. So this command will create users table under MySQL database from this Laravel 9 Application.


php artisan migrate


Now we want to add four table column in users table, so for this, in command prompt we have to following command.


php artisan make:migration update_users_table --table=users

So this command has been create alter user table migration file and under this file we have to define column details which you can seen below.

database/migrations/2022_09_06_111722_update_users_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('token');
            $table->integer('connection_id');
            $table->enum('user_status', ['Offline', 'Online']);
            $table->string('user_image');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('token');
            $table->dropColumn('connection_id');
            $table->dropColumn('user_status');
            $table->dropColumn('user_image');
        });
    }
};



Next We need to create chats table, so for this, we have to goest to command prompt and run following command.


php artisan make:model Chat -m


So this command will create migration file and Chat Model Class file. First we have to open Chats table migration file and under this file, we have to define table column defination.

database/migrations/2022_09_06_112831_create_chats_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('chats', function (Blueprint $table) {
            $table->id();
            $table->integer('from_user_id');
            $table->integer('to_user_id');
            $table->string('chat_message');
            $table->string('message_status');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('chats');
    }
};



And in MySQL database part, we have to create chat_requests table. So for this, we have goes to command prompt and run following command.


php artisan make:model Chat_request -m


This command will make create chat_requests table migration file and Chat_request.php Model class file. Here first we have to open create chat_requests table migration file and under this file, we have to define table column details which you can seen below.


<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('chat_requests', function (Blueprint $table) {
            $table->id();
            $table->integer('from_user_id');
            $table->integer('to_user_id');
            $table->enum('status', ['Pending', 'Approve', 'Reject']);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('chat_requests');
    }
};



Now we want to create two new table and alter existing table, so for this we have goes to command prompt and run following command.


php artisan migrate


So this command will create chats table and chat_requests table and add four table column in users table from this laravel application.

Step 3 - Create Model Class


Under this Laravel Chat Application we will use Model class for database operation. Model class file will be created when we have create MySQL table migration file. Under this tutorial, we have create User.php, Chat.php and Chat_request.php model class file. So under this file, we have to define MySQL table column details, which you can seen below.

app/Models/User.php

<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
        'token',
        'connection_id',
        'user_status',
        'user_image'
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}



app/Models/Chat.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Chat extends Model
{
    use HasFactory;

    protected $fillable = ['from_user_id', 'to_user_id', 'chat_message', 'message_status'];
}



app/Models/Chat_request.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Chat_request extends Model
{
    use HasFactory;

    protected $fillable = ['from_user_id', 'to_user_id', 'status'];
}



Step 4 - Download & Install Ratchet WebSocket Library


In this Laravel Chat Application, we have use Ratchet WebSocket Library for make Real-time Chat Application. So in this part, we will download and install Ratchet WebSocket Library. So for download Ratchet WebSocket Library. We have goes to command prompt and run following command.


composer require cboden/ratchet


This command will download and install Ratchet Websocket Library under vendor. After this we want to create WebSocket Server file. So for this, we have to goes to command prompt and run following file.


php artisan make:command WebSocketServer --command=websocket:init


After run above command, so it will create Websocket Server file in app/Console/Commands/WebSocketServer.php under this directory. So we have to open this file, and under this file we have to define following code for make Ratchet WebSocket server.

app/Console/Commands/WebSocketServer.php

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

use Ratchet\Server\IoServer;

use Ratchet\Http\HttpServer;

use Ratchet\WebSocket\WsServer;

use React\EventLoop\Factory;

use App\Http\Controllers\SocketController;

class WebSocketServer extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'websocket:init';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        //return 0;

        $server = IoServer::factory(
            new HttpServer(
                new WsServer(
                    new SocketController()
                )
            ),
            8090
        );

        $server->run();
    }
}



So this WebSocket Server will be run on the 8090 port number. Next we have to create Socket Controller file. So for this, we have goes to command prompt and run following command.


php artisan make:controller SocketController


So this command will make SocketController.php controller file under app/Http/Controllers directory, and you can find source code of this controller file under this Create Controller Steps.





Step 5 - Create Controller


Under this Laravel Ratchet Chat Application, first we want to make controller for Login and Registration page. So for create Controller for Chat Application Login and Registration page, we have goes to command prompt and run following command.


php artisan make:controller SampleController


This command will make SampleController.php file under app/Http/Controllers directory. So we have to open this directory and write following code for make Login Registration and Logout feature for this Real-time Laravel Ratchet WebSocket Chat Application.

app/Http/Controllers/SampleController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use Hash;
use Session;
use App\Models\User;
use Illuminate\Support\Facades\Auth;

class SampleController extends Controller
{
    function index()
    {
        return view('login');
    }

    function registration()
    {
        return view('registration');
    }

    function validate_registration(Request $request)
    {
        $request->validate([
            'name'         =>   'required',
            'email'        =>   'required|email|unique:users',
            'password'     =>   'required|min:6'
        ]);

        $data = $request->all();

        User::create([
            'name'  =>  $data['name'],
            'email' =>  $data['email'],
            'password' => Hash::make($data['password'])
        ]);

        return redirect('login')->with('success', 'Registration Completed, now you can login');
    }

    function validate_login(Request $request)
    {
        $request->validate([
            'email' =>  'required',
            'password'  =>  'required'
        ]);

        $credentials = $request->only('email', 'password');

        if(Auth::attempt($credentials))
        {
            $token = md5(uniqid());

            User::where('id', Auth::id())->update([ 'token' => $token ]);

            return redirect('dashboard');
        }

        return redirect('login')->with('success', 'Login details are not valid');
    }

    function dashboard()
    {
        if(Auth::check())
        {
            return view('dashboard');
        }

        return redirect('login')->with('success', 'you are not allowed to access');
    }

    function logout()
    {
        Session::flush();

        Auth::logout();

        return Redirect('login');
    }

    public function profile()
    {
        if(Auth::check())
        {
            $data = User::where('id', Auth::id())->get();

            return view('profile', compact('data'));
        }

        return redirect("login")->with('success', 'you are not allowed to access');
    }

    public function profile_validation(Request $request)
    {
        $request->validate([
            'name'      =>  'required',
            'email'     =>  'required|email',
            'user_image'   =>   'image|mimes:jpg,png,jpeg|max:2048|dimensions:min_width=100,min_height=100,max_width=1000,max_height=1000'
        ]);

        $user_image = $request->hidden_user_image;

        if($request->user_image != '')
        {
            $user_image = time() . '.' . $request->user_image->getClientOriginalExtension();

            $request->user_image->move(public_path('images'), $user_image);
        }

        $user = User::find(Auth::id());

        $user->name = $request->name;

        $user->email = $request->email;

        if($request->password != '')
        {
            $user->password = Hash::make($request->password);
        }

        $user->user_image = $user_image;

        $user->save();

        return redirect('profile')->with('success', 'Profile Details Updated');
    }
}



app/Http/Controllers/SocketController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use Ratchet\MessageComponentInterface;

use Ratchet\ConnectionInterface;

use App\Models\User;

use App\Models\Chat;

use App\Models\Chat_request;

use Auth;

class SocketController extends Controller implements MessageComponentInterface
{
    protected $clients;

    public function __construct()
    {
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn)
    {
        $this->clients->attach($conn);

        $querystring = $conn->httpRequest->getUri()->getQuery();

        parse_str($querystring, $queryarray);

        if(isset($queryarray['token']))
        {
            User::where('token', $queryarray['token'])->update([ 'connection_id' => $conn->resourceId ]);
        }


    }

    public function onMessage(ConnectionInterface $conn, $msg)
    {
        $data = json_decode($msg);

        if(isset($data->type))
        {
            if($data->type == 'request_load_unconnected_user')
            {
                $user_data = User::select('id', 'name', 'user_status', 'user_image')
                                    ->where('id', '!=', $data->from_user_id)
                                    ->orderBy('name', 'ASC')
                                    ->get();

                $sub_data = array();

                foreach($user_data as $row)
                {
                    $sub_data[] = array(
                        'name'      =>  $row['name'],
                        'id'        =>   $row['id'],
                        'status'    =>  $row['user_status'],
                        'user_image'=>  $row['user_image']
                    );
                }

                $sender_connection_id = User::select('connection_id')->where('id', $data->from_user_id)->get();

                $send_data['data'] = $sub_data;

                $send_data['response_load_unconnected_user'] = true;

                foreach($this->clients as $client)
                {
                    if($client->resourceId == $sender_connection_id[0]->connection_id)
                    {
                        $client->send(json_encode($send_data));
                    }
                }
            }

            if($data->type == 'request_search_user')
            {
                $user_data = User::select('id', 'name', 'user_status', 'user_image')
                                    ->where('id', '!=', $data->from_user_id)
                                    ->where('name', 'like', '%'.$data->search_query.'%')
                                    ->orderBy('name', 'ASC')
                                    ->get();

                $sub_data = array();

                foreach($user_data as $row)
                {

                    $chat_request = Chat_request::select('id')
                                    ->where(function($query) use ($data, $row){
                                        $query->where('from_user_id', $data->from_user_id)->where('to_user_id', $row->id);
                                    })
                                    ->orWhere(function($query) use ($data, $row){
                                        $query->where('from_user_id', $row->id)->where('to_user_id', $data->from_user_id);
                                    })->get();

                    /*
                    SELECT id FROM chat_request 
                    WHERE (from_user_id = $data->from_user_id AND to_user_id = $row->id) 
                    OR (from_user_id = $row->id AND to_user_id = $data->from_user_id)
                    */

                    if($chat_request->count() == 0)
                    {
                        $sub_data[] = array(
                            'name'  =>  $row['name'],
                            'id'    =>  $row['id'],
                            'status'=>  $row['user_status'],
                            'user_image' => $row['user_image']
                        );
                    }

                    
                }

                $sender_connection_id = User::select('connection_id')->where('id', $data->from_user_id)->get();

                $send_data['data'] = $sub_data;

                $send_data['response_search_user'] = true;

                foreach($this->clients as $client)
                {
                    if($client->resourceId == $sender_connection_id[0]->connection_id)
                    {
                        $client->send(json_encode($send_data));
                    }
                }
            }

            if($data->type == 'request_chat_user')
            {
                $chat_request = new Chat_request;

                $chat_request->from_user_id = $data->from_user_id;

                $chat_request->to_user_id = $data->to_user_id;

                $chat_request->status = 'Pending';

                $chat_request->save();

                $sender_connection_id = User::select('connection_id')->where('id', $data->from_user_id)->get();

                $receiver_connection_id = User::select('connection_id')->where('id', $data->to_user_id)->get();

                foreach($this->clients as $client)
                {
                    if($client->resourceId == $sender_connection_id[0]->connection_id)
                    {
                        $send_data['response_from_user_chat_request'] = true;

                        $client->send(json_encode($send_data));
                    }

                    if($client->resourceId == $receiver_connection_id[0]->connection_id)
                    {
                        $send_data['user_id'] = $data->to_user_id;

                        $send_data['response_to_user_chat_request'] = true;

                        $client->send(json_encode($send_data));
                    }
                }
            }

            if($data->type == 'request_load_unread_notification')
            {
                $notification_data = Chat_request::select('id', 'from_user_id', 'to_user_id', 'status')
                                        ->where('status', '!=', 'Approve')
                                        ->where(function($query) use ($data){
                                            $query->where('from_user_id', $data->user_id)->orWhere('to_user_id', $data->user_id);
                                        })->orderBy('id', 'ASC')->get();

                /*
                SELECT id, from_user_id, to_user_id, status FROM chat_requests
                WHERE status != 'Approve'
                AND (from_user_id = $data->user_id OR to_user_id = $data->user_id)
                ORDER BY id ASC
                */

                $sub_data = array();

                foreach($notification_data as $row)
                {
                    $user_id = '';

                    $notification_type = '';

                    if($row->from_user_id == $data->user_id)
                    {
                        $user_id = $row->to_user_id;

                        $notification_type = 'Send Request';
                    }
                    else
                    {
                        $user_id = $row->from_user_id;

                        $notification_type = 'Receive Request';
                    }

                    $user_data = User::select('name', 'user_image')->where('id', $user_id)->first();

                    $sub_data[] = array(
                        'id'            =>  $row->id,
                        'from_user_id'  =>  $row->from_user_id,
                        'to_user_id'    =>  $row->to_user_id,
                        'name'          =>  $user_data->name,
                        'notification_type' =>  $notification_type,
                        'status'           =>   $row->status,
                        'user_image'    =>  $user_data->user_image
                    );
                }

                $sender_connection_id = User::select('connection_id')->where('id', $data->user_id)->get();

                foreach($this->clients as $client)
                {
                    if($client->resourceId == $sender_connection_id[0]->connection_id)
                    {
                        $send_data['response_load_notification'] = true;

                        $send_data['data'] = $sub_data;

                        $client->send(json_encode($send_data));
                    }
                }
            }

            if($data->type == 'request_process_chat_request')
            {
                Chat_request::where('id', $data->chat_request_id)->update(['status' => $data->action]);

                $sender_connection_id = User::select('connection_id')->where('id', $data->from_user_id)->get();

                $receiver_connection_id = User::select('connection_id')->where('id', $data->to_user_id)->get();

                foreach($this->clients as $client)
                {
                    $send_data['response_process_chat_request'] = true;

                    if($client->resourceId == $sender_connection_id[0]->connection_id)
                    {
                        $send_data['user_id'] = $data->from_user_id;
                    }

                    if($client->resourceId == $receiver_connection_id[0]->connection_id)
                    {
                        $send_data['user_id'] = $data->to_user_id;
                    }

                    $client->send(json_encode($send_data));
                }
            }

            if($data->type == 'request_connected_chat_user')
            {
                $condition_1 = ['from_user_id' => $data->from_user_id, 'to_user_id' => $data->from_user_id];

                $user_id_data = Chat_request::select('from_user_id', 'to_user_id')
                                            ->orWhere($condition_1)
                                            ->where('status', 'Approve')
                                            ->get();

                /*
                SELECT from_user id, to_user_id FROM chat_requests 
                WHERE (from_user_id = $data->from_user_id OR to_user_id = $data->from_user_id) 
                AND status = 'Approve'
                */

                $sub_data = array();

                foreach($user_id_data as $user_id_row)
                {
                    $user_id = '';

                    if($user_id_row->from_user_id != $data->from_user_id)
                    {
                        $user_id = $user_id_row->from_user_id;
                    }
                    else
                    {
                        $user_id = $user_id_row->to_user_id;
                    }

                    $user_data = User::select('id', 'name', 'user_image')->where('id', $user_id)->first();

                    $sub_data[] = array(
                        'id'    =>  $user_data->id,
                        'name'  =>  $user_data->name,
                        'user_image'    =>  $user_data->user_image
                    );


                }

                $sender_connection_id = User::select('connection_id')->where('id', $data->from_user_id)->get();

                foreach($this->clients as $client)
                {
                    if($client->resourceId == $sender_connection_id[0]->connection_id)
                    {
                        $send_data['response_connected_chat_user'] = true;

                        $send_data['data'] = $sub_data;

                        $client->send(json_encode($send_data));
                    }
                }
            }

            if($data->type == 'request_send_message')
            {
                //save chat message in mysql

                $chat = new Chat;

                $chat->from_user_id = $data->from_user_id;

                $chat->to_user_id = $data->to_user_id;

                $chat->chat_message = $data->message;

                $chat->message_status = 'Not Send';

                $chat->save();

                $chat_message_id = $chat->id;

                $receiver_connection_id = User::select('connection_id')->where('id', $data->to_user_id)->get();

                $sender_connection_id = User::select('connection_id')->where('id', $data->from_user_id)->get();

                foreach($this->clients as $client)
                {
                    if($client->resourceId == $receiver_connection_id[0]->connection_id || $client->resourceId == $sender_connection_id[0]->connection_id)
                    {
                        $send_data['chat_message_id'] = $chat_message_id;
                        
                        $send_data['message'] = $data->message;

                        $send_data['from_user_id'] = $data->from_user_id;

                        $send_data['to_user_id'] = $data->to_user_id;

                        if($client->resourceId == $receiver_connection_id[0]->connection_id)
                        {
                            Chat::where('id', $chat_message_id)->update(['message_status' =>'Send']);

                            $send_data['message_status'] = 'Send';
                        }
                        else
                        {
                            $send_data['message_status'] = 'Not Send';
                        }

                        $client->send(json_encode($send_data));
                    }
                }
            }

            if($data->type == 'request_chat_history')
            {
                $chat_data = Chat::select('id', 'from_user_id', 'to_user_id', 'chat_message', 'message_status')
                                    ->where(function($query) use ($data){
                                        $query->where('from_user_id', $data->from_user_id)->where('to_user_id', $data->to_user_id);
                                    })
                                    ->orWhere(function($query) use ($data){
                                        $query->where('from_user_id', $data->to_user_id)->where('to_user_id', $data->from_user_id);
                                    })->orderBy('id', 'ASC')->get();
                /*
                SELECT id, from_user_id, to_user_id, chat_message, message status 
                FROM chats 
                WHERE (from_user_id = $data->from_user_id AND to_user_id = $data->to_user_id) 
                OR (from_user_id = $data->to_user_id AND to_user_id = $data->from_user_id)
                ORDER BY id ASC
                */

                $send_data['chat_history'] = $chat_data;

                $receiver_connection_id = User::select('connection_id')->where('id', $data->from_user_id)->get();

                foreach($this->clients as $client)
                {
                    if($client->resourceId == $receiver_connection_id[0]->connection_id)
                    {
                        $client->send(json_encode($send_data));
                    }
                }

            }

            if($data->type == 'update_chat_status')
            {
                //update chat status

                Chat::where('id', $data->chat_message_id)->update(['message_status' => $data->chat_message_status]);

                $sender_connection_id = User::select('connection_id')->where('id', $data->from_user_id)->get();

                foreach($this->clients as $client)
                {
                    if($client->resourceId == $sender_connection_id[0]->connection_id)
                    {
                        $send_data['update_message_status'] = $data->chat_message_status;

                        $send_data['chat_message_id'] = $data->chat_message_id;

                        $client->send(json_encode($send_data));
                    }
                }
            }
        }
    }

    public function onClose(ConnectionInterface $conn)
    {
        $this->clients->detach($conn);

        $querystring = $conn->httpRequest->getUri()->getQuery();

        parse_str($querystring, $queryarray);

        if(isset($queryarray['token']))
        {
            User::where('token', $queryarray['token'])->update([ 'connection_id' => 0 ]);
        }
    }

    public function onError(ConnectionInterface $conn, \Exception $e)
    {
        echo "An error has occurred: {$e->getMessage()} \n";

        $conn->close();
    }
}



Step 6 - Create View Blades Files


In this steps, we have to make Blade views file for display HTML output in the browser. So first we have to make Login, Registration and Master template for this Laravel Chat Application. So we have goes to resources/views directory and under this directory, we have to make following views blade file.

resources/views/main.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Laravel 9 Custom Login Registration</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>

    <nav class="navbar navbar-light navbar-expand-lg mb-5" style="background-color: #e3f2fd;">
        <div class="container">
            <a class="navbar-brand mr-auto" href="#">Chat Application</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                    
                <ul class="navbar-nav">
                    @guest

                    <li class="nav-item">
                        <a class="nav-link" href="{{ route('login') }}">Login</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{{ route('registration') }}">Register</a>
                    </li>

                    @else

                    <li class="nav-item">
                        @if(Auth::user()->user_image != '')
                        <a class="nav-link" href="#"><b>Welcome <img src="{{ asset('images/' . Auth::user()->user_image ) }}" width="35" class="rounded-circle" />&nbsp; {{ Auth::user()->name }}</b></a>
                        @else
                        <a class="nav-link" href="#"><b>Welcome <img src="{{ asset('images/no-image.jpg') }}" width="35" class="rounded-circle" />&nbsp;{{ Auth::user()->name }}</b></a>
                        @endif
                    </li>

                    <li class="nav-item">
                        <a class="nav-link" href="{{ route('profile') }}">Profile</a>
                    </li>

                    <li class="nav-item">
                        <a class="nav-link" href="{{ route('logout') }}">Logout</a>
                    </li>

                    @endguest
                </ul>
                
            </div>
        </div>
    </nav>
    <div class="container-fluid mt-5">

        @yield('content')
        
    </div>
    
</body>
</html>


resources/views/login.blade.php

@extends('main')

@section('content')

@if($message = Session::get('success'))

<div class="alert alert-info">
{{ $message }}
</div>

@endif

<div class="row justify-content-center">
	<div class="col-md-4">
		<div class="card">
			<div class="card-header">Login</div>
			<div class="card-body">
				<form action="{{ route('sample.validate_login') }}" method="post">
					@csrf
					<div class="form-group mb-3">
						<input type="text" name="email" class="form-control" placeholder="Email" />
						@if($errors->has('email'))
							<span class="text-danger">{{ $errors->first('email') }}</span>
						@endif
					</div>
					<div class="form-group mb-3">
						<input type="password" name="password" class="form-control" placeholder="Password" />
						@if($errors->has('password'))
							<span class="text-danger">{{ $errors->first('password') }}</span>
						@endif
					</div>
					<div class="d-grid mx-auto">
						<button type="subit" class="btn btn-dark btn-block">Login</button>
					</div>
				</form>
			</div>
		</div>
	</div>
</div>

@endsection('content')


resources/views/registration.blade.php

@extends('main')

@section('content')

<div class="row justify-content-center">
	<div class="col-md-4">
		<div class="card">
		<div class="card-header">Registration</div>
		<div class="card-body">
			<form action="{{ route('sample.validate_registration') }}" method="POST">
				@csrf
				<div class="form-group mb-3">
					<input type="text" name="name" class="form-control" placeholder="Name" />
					@if($errors->has('name'))
						<span class="text-danger">{{ $errors->first('name') }}</span>
					@endif
				</div>
				<div class="form-group mb-3">
					<input type="text" name="email" class="form-control" placeholder="Email Address" />
					@if($errors->has('email'))
						<span class="text-danger">{{ $errors->first('email') }}</span>
					@endif
				</div>
				<div class="form-group mb-3">
					<input type="password" name="password" class="form-control" placeholder="Password" />
					@if($errors->has('password'))
						<span class="text-danger">{{ $errors->first('password') }}</span>
					@endif
				</div>
				<div class="d-grid mx-auto">
					<button type="submit" class="btn btn-dark btn-block">Register</button>
				</div>
			</form>
		</div>
	</div>
</div>

@endsection('content')


resources/views/dashboard.php

@extends('main')

@section('content')

<div class="row">
	<div class="col-sm-4 col-lg-3">
		<div class="card">
			<div class="card-header"><b>Connected User</b></div>
			<div class="card-body" id="user_list">
				
			</div>
		</div>
	</div>
	<div class="col-sm-4 col-lg-6">
		<div class="card">
			<div class="card-header">
				<div class="row">
					<div class="col col-md-6" id="chat_header"><b>Chat Area</b></div>
					<div class="col col-md-6" id="close_chat_area"></div>
				</div>
			</div>
			<div class="card-body" id="chat_area">
				
			</div>
		</div>
	</div>
	<div class="col-sm-4 col-lg-3">
		<div class="card" style="height:255px; overflow-y: scroll;">
			<div class="card-header">
				<input type="text" class="form-control" placeholder="Search User..." autocomplete="off" id="search_people" onkeyup="search_user('{{ Auth::id() }}', this.value);" />
			</div>
			<div class="card-body">
				<div id="search_people_area" class="mt-3"></div>
			</div>
		</div>
		<br />
		<div class="card" style="height:255px; overflow-y: scroll;">
			<div class="card-header"><b>Notification</b></div>
			<div class="card-body">
				<ul class="list-group" id="notification_area">
					
				</ul>
			</div>
		</div>
	</div>
</div>

<style>

#chat_area
{
	min-height: 500px;
	/*overflow-y: scroll*/;
}

#chat_history
{
	min-height: 500px; 
	max-height: 500px; 
	overflow-y: scroll; 
	margin-bottom:16px; 
	background-color: #ece5dd;
	padding: 16px;
}

#user_list
{
	min-height: 500px; 
	max-height: 500px; 
	overflow-y: scroll;
}
</style>

@endsection('content')

<script>

var conn = new WebSocket('ws://127.0.0.1:8090/?token={{ auth()->user()->token }}');

var from_user_id = "{{ Auth::user()->id }}";

var to_user_id = "";

conn.onopen = function(e){

	console.log("Connection established!");

	load_unconnected_user(from_user_id);

	load_unread_notification(from_user_id);

	load_connected_chat_user(from_user_id);

};

conn.onmessage = function(e){

	var data = JSON.parse(e.data);

	if(data.response_load_unconnected_user || data.response_search_user)
	{
		var html = '';

		if(data.data.length > 0)
		{
			html += '<ul class="list-group">';

			for(var count = 0; count < data.data.length; count++)
			{
				var user_image = '';

				if(data.data[count].user_image != '')
				{
					user_image = `<img src="{{ asset('images/') }}/`+data.data[count].user_image+`" width="40" class="rounded-circle" />`;
				}
				else
				{
					user_image = `<img src="{{ asset('images/no-image.jpg') }}" width="40" class="rounded-circle" />`
				}

				html += `
				<li class="list-group-item">
					<div class="row">
						<div class="col col-9">`+user_image+`&nbsp;`+data.data[count].name+`</div>
						<div class="col col-3">
							<button type="button" name="send_request" class="btn btn-primary btn-sm float-end" onclick="send_request(this, `+from_user_id+`, `+data.data[count].id+`)"><i class="fas fa-paper-plane"></i></button>
						</div>
					</div>
				</li>
				`;
			}

			html += '</ul>';
		}
		else
		{
			html = 'No User Found';
		}

		document.getElementById('search_people_area').innerHTML = html;
	}

	if(data.response_from_user_chat_request)
	{
		search_user(from_user_id, document.getElementById('search_people').value);

		load_unread_notification(from_user_id);
	}

	if(data.response_to_user_chat_request)
	{
		load_unread_notification(data.user_id);
	}

	if(data.response_load_notification)
	{
		var html = '';

		for(var count = 0; count < data.data.length; count++)
		{
			var user_image = '';

			if(data.data[count].user_image != '')
			{
				user_image = `<img src="{{ asset('images/') }}/`+data.data[count].user_image+`" width="40" class="rounded-circle" />`;
			}
			else
			{
				user_image = `<img src="{{ asset('images/no-image.jpg') }}" width="40" class="rounded-circle" />`;
			}

			html += `
			<li class="list-group-item">
				<div class="row">
					<div class="col col-8">`+user_image+`&nbsp;`+data.data[count].name+`</div>
					<div class="col col-4">
			`;
			if(data.data[count].notification_type == 'Send Request')
			{
				if(data.data[count].status == 'Pending')
				{
					html += '<button type="button" name="send_request" class="btn btn-warning btn-sm float-end">Request Send</button>';
				}
				else
				{
					html += '<button type="button" name="send_request" class="btn btn-danger btn-sm float-end">Request Rejected</button>';
				}
			}
			else
			{
				if(data.data[count].status == 'Pending')
				{
					html += '<button type="button" class="btn btn-danger btn-sm float-end" onclick="process_chat_request('+data.data[count].id+', '+data.data[count].from_user_id+', '+data.data[count].to_user_id+', `Reject`)"><i class="fas fa-times"></i></button>&nbsp;';
					html += '<button type="button" class="btn btn-success btn-sm float-end" onclick="process_chat_request('+data.data[count].id+', '+data.data[count].from_user_id+', '+data.data[count].to_user_id+', `Approve`)"><i class="fas fa-check"></i></button>';
				}
				else
				{
					html += '<button type="button" name="send_request" class="btn btn-danger btn-sm float-end">Request Rejected</button>';
				}
			}

			html += `
					</div>
				</div>
			</li>
			`;
		}

		document.getElementById('notification_area').innerHTML = html;
	}

	if(data.response_process_chat_request)
	{
		load_unread_notification(data.user_id);

		load_connected_chat_user(data.user_id);
	}

	if(data.response_connected_chat_user)
	{
		var html = '<div class="list-group">';

		if(data.data.length > 0)
		{
			for(var count = 0; count < data.data.length; count++)
			{
				html += `
				<a href="#" class="list-group-item d-flex justify-content-between align-items-start" onclick="make_chat_area(`+data.data[count].id+`, '`+data.data[count].name+`'); load_chat_data(`+from_user_id+`, `+data.data[count].id+`); ">
					<div class="ms-2 me-auto">
				`;

				var user_image = '';

				if(data.data[count].user_image != '')
				{
					user_image = `<img src="{{ asset('images/') }}/`+data.data[count].user_image+`" width="35" class="rounded-circle" />`;
				}
				else
				{
					user_image = `<img src="{{ asset('images/no-image.jpg') }}" width="35" class="rounded-circle" />`;
				}



				html += `
						&nbsp; `+user_image+`&nbsp;<b>`+data.data[count].name+`</b>
					</div>
				</a>
				`;
			}
		}
		else
		{
			html += 'No User Found';
		}

		html += '</div>';

		document.getElementById('user_list').innerHTML = html;
	}

	if(data.message)
	{
		var html = '';

		if(data.from_user_id == from_user_id)
		{

			var icon_style = '';

			if(data.message_status == 'Not Send')
			{
				icon_style = '<span id="chat_status_'+data.message_status+'" class="float-end"><i class="fas fa-check text-muted"></i></span>';
			}
			if(data.message_status == 'Send')
			{
				icon_style = '<span id="chat_status_'+data.message_status+'" class="float-end"><i class="fas fa-check-double text-muted"></i></span>';
			}

			if(data.message_status == 'Read')
			{
				icon_style = '<span class="text-primary float-end" id="chat_status_'+data.message_status+'"><i class="fas fa-check-double"></i></span>';
			}

			html += `
			<div class="row">
				<div class="col col-3">&nbsp;</div>
				<div class="col col-9 alert alert-success text-dark shadow-sm">
					`+data.message+ icon_style +`
				</div>
			</div>
			`;
		}
		else
		{
			if(to_user_id != '')
			{
				html += `
				<div class="row">
					<div class="col col-9 alert alert-light text-dark shadow-sm">
					`+data.message+`
					</div>
				</div>
				`;

				update_message_status(data.chat_message_id, from_user_id, to_user_id, 'Read');
			}
			else
			{

			}
			
		}

		if(html != '')
		{
			var previous_chat_element = document.querySelector('#chat_history');

			var chat_history_element = document.querySelector('#chat_history');

			chat_history_element.innerHTML = previous_chat_element.innerHTML + html;
		}
		scroll_top();
	}

	if(data.chat_history)
	{
		var html = '';

		for(var count = 0; count < data.chat_history.length; count++)
		{
			if(data.chat_history[count].from_user_id == from_user_id)
			{
				var icon_style = '';

				if(data.chat_history[count].message_status == 'Not Send')
				{
					icon_style = '<span id="chat_status_'+data.chat_history[count].id+'" class="float-end"><i class="fas fa-check text-muted"></i></span>';
				}

				if(data.chat_history[count].message_status == 'Send')
				{
					icon_style = '<span id="chat_status_'+data.chat_history[count].id+'" class="float-end"><i class="fas fa-check-double text-muted"></i></span>';
				}

				if(data.chat_history[count].message_status == 'Read')
				{
					icon_style = '<span class="text-primary float-end" id="chat_status_'+data.chat_history[count].id+'"><i class="fas fa-check-double"></i></span>';
				}

				html +=`
				<div class="row">
					<div class="col col-3">&nbsp;</div>
					<div class="col col-9 alert alert-success text-dark shadow-sm">
					`+data.chat_history[count].chat_message+ icon_style + `
					</div>
				</div>
				`;
			}
			else
			{
				if(data.chat_history[count].message_status != 'Read')
				{
					update_message_status(data.chat_history[count].id, data.chat_history[count].from_user_id, data.chat_history[count].to_user_id, 'Read');
				}

				html += `
				<div class="row">
					<div class="col col-9 alert alert-light text-dark shadow-sm">
					`+data.chat_history[count].chat_message+`
					</div>
				</div>
				`;
			}
		}

		document.querySelector('#chat_history').innerHTML = html;

		scroll_top();
	}

	if(data.update_message_status)
	{
		var chat_status_element = document.querySelector('#chat_status_'+data.chat_message_id+'');

		if(chat_status_element)
		{
			if(data.update_message_status == 'Read')
			{
				chat_status_element.innerHTML = '<i class="fas fa-check-double text-primary"></i>';
			}
			if(data.update_message_status == 'Send')
			{
				chat_status_element.innerHTML = '<i class="fas fa-check-double text-muted"></i>';
			}
		}
	}
};

function scroll_top()
{
    document.querySelector('#chat_history').scrollTop = document.querySelector('#chat_history').scrollHeight;
}

function load_unconnected_user(from_user_id)
{
	var data = {
		from_user_id : from_user_id,
		type : 'request_load_unconnected_user'
	};

	conn.send(JSON.stringify(data));
}

function search_user(from_user_id, search_query)
{
	if(search_query.length > 0)
	{
		var data = {
			from_user_id : from_user_id,
			search_query : search_query,
			type : 'request_search_user'
		};

		conn.send(JSON.stringify(data));
	}
	else
	{
		load_unconnected_user(from_user_id);
	}
}

function send_request(element, from_user_id, to_user_id)
{
	var data = {
		from_user_id : from_user_id,
		to_user_id : to_user_id,
		type : 'request_chat_user'
	};

	element.disabled = true;

	conn.send(JSON.stringify(data));
}

function load_unread_notification(user_id)
{
	var data = {
		user_id : user_id,
		type : 'request_load_unread_notification'
	};

	conn.send(JSON.stringify(data));

}

function process_chat_request(chat_request_id, from_user_id, to_user_id, action)
{
	var data = {
		chat_request_id : chat_request_id,
		from_user_id : from_user_id,
		to_user_id : to_user_id,
		action : action,
		type : 'request_process_chat_request'
	};

	conn.send(JSON.stringify(data));
}

function load_connected_chat_user(from_user_id)
{
	var data = {
		from_user_id : from_user_id,
		type : 'request_connected_chat_user'
	};

	conn.send(JSON.stringify(data));
}

function make_chat_area(user_id, to_user_name)
{
	var html = `
	<div id="chat_history"></div>
	<div class="input-group mb-3">
		<textarea id="message_area" class="form-control" aria-describedby="send_button"></textarea>
		<button type="button" class="btn btn-success" id="send_button" onclick="send_chat_message()"><i class="fas fa-paper-plane"></i></button>
	</div>
	`;

	document.getElementById('chat_area').innerHTML = html;

	document.getElementById('chat_header').innerHTML = 'Chat with <b>'+to_user_name+'</b>';

	document.getElementById('close_chat_area').innerHTML = '<button type="button" id="close_chat" class="btn btn-danger btn-sm float-end" onclick="close_chat();"><i class="fas fa-times"></i></button>';

	to_user_id = user_id;
}

function close_chat()
{
	document.getElementById('chat_header').innerHTML = 'Chat Area';

	document.getElementById('close_chat_area').innerHTML = '';

	document.getElementById('chat_area').innerHTML = '';

	to_user_id = '';
}

function send_chat_message()
{
	document.querySelector('#send_button').disabled = true;

	var message = document.getElementById('message_area').value.trim();

	var data = {
		message : message,
		from_user_id : from_user_id,
		to_user_id : to_user_id,
		type : 'request_send_message'
	};

	conn.send(JSON.stringify(data));

	document.querySelector('#message_area').value = '';

	document.querySelector('#send_button').disabled = false;
}

function load_chat_data(from_user_id, to_user_id)
{
	var data = {
		from_user_id : from_user_id,
		to_user_id : to_user_id,
		type : 'request_chat_history'
	};

	conn.send(JSON.stringify(data));
}

function update_message_status(chat_message_id, from_user_id, to_user_id, chat_message_status)
{
	var data = {
		chat_message_id : chat_message_id,
		from_user_id : from_user_id,
		to_user_id : to_user_id,
		chat_message_status : chat_message_status,
		type : 'update_chat_status'
	};

	conn.send(JSON.stringify(data));
}

</script>


Step 7 - Set Route of Controller Method


Next we have to set route of Controllers class method. So for this, we have to open routes/web.php and under this file, we have to write following code for set route for Controllers Class method.

routes/web.php

<?php

use Illuminate\Support\Facades\Route;

use App\Http\Controllers\SampleController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::controller(SampleController::class)->group(function(){

    Route::get('login', 'index')->name('login');

    Route::get('registration', 'registration')->name('registration');

    Route::get('logout', 'logout')->name('logout');

    Route::post('validate_registration', 'validate_registration')->name('sample.validate_registration');

    Route::post('validate_login', 'validate_login')->name('sample.validate_login');

    Route::get('dashboard', 'dashboard')->name('dashboard');

    Route::get('profile', 'profile')->name('profile');

    Route::post('profile_validation', 'profile_validation')->name('sample.profile_validation');

});



Step 8 - Run Laravel 9 Application


Now we have come to last step and under this step we have to check output in the browser. So before check output in the browser, first we need to start Laravel server. So we have goes to command prompt and run following command.


php artisan serve


This command will start Laravel server and it will provide use base url of our Laravel Ratchet WebScoket Chat Application, so we have goes to browser and paste following url in browser for check output in the browser.


http://127.0.0.1:8000/login


This is partial code and we will update remaining code on every publish of video tutorial. So check this tutorial, on every publish of new video tutorial of this Real-time Laravel Ratchet WebSocket Chat Application.





2 comments: