Realtime chat application in Laravel, React and Pusher

Last updated : May 05, 2023

Realtime chat application in Laravel, React and Pusher



In this article you will know how to develop a real time chat application using Laravel, React and Pusher. This article gives you all base scripts which you can enhance and write real time chat apps.

React:

React is a frontend development library built by Facebook. According to https://reactjs.org, “React is a Javascript library for building user interfaces”.

 

Laravel:

According to https://laravel.com, “Laravel is a web application framework with expressive, elegant syntax.”

It helps in creating the small website, or complex applications with ease and really quick.

 

We are using latest version of Laravel.

 

Prerequisites:

1. Laravel installed on your system (If not installed, see how its done)

2. Node & NPM installed on your system

3. Pusher account and an app setup (Pusher account is fee and setting up an app is simple) (If not setup, see how its done)

 

What we are going to do in this app:
 

1. Firstly, create a laravel app

2. Then, we will install package ‘pusher-php-server’

3. After that we will install another package ‘laravel/ui’ for basic authentication with React.

4.Create two models i.e, User(which is pre-created in Laravel) and Message.

5.Then we will create a controller file for controlling database and requests.

6.Then we will create an event which will be triggered for sending message to pusher for broadcasting to other user in real time.

 

Aright, lot of theory, now lets make this work.

1.Go to Pusher dashboard and create an account and then create an app.

 

2.Open terminal/command prompt with admin privileges and type laravel new chat

 

3.type cd chat

 

4.now run command composer require laravel/ui and composer require pusher-php-server

 

5.then open your laravel app in any code editor of your choice

 

6.Then we will enable auth scaffolding with reactjs. for this open terminal and type php artisan ui react --auth.

After this you will have certain changes in your app i.e., new files will be added to views folder and

controller folder and basic user migration will be created.

 

7.now open app/Providers/RouteServiceProvider.php file and find line public const HOME change it to public const HOME='/';

 

8.then run commands npm i and npm run dev in terminal/command prompt to complete scaffolding

 

9.then open .env file and change broadcast driver to pusher

BROADCAST_DRIVER=pusher

 

10. now go to app/config/app.php and in providers section add/uncomment line

 

Illuminate\Broadcasting\BroadcastServiceprovider::class

 

and outside providers section , add/uncomment line

 

App\Providers\BroadcastServiceProvider::class

 

 

11. now add pusher app details in .env file as given in pusher.com, when you created app in pusher

 

PUSHER_APP_ID=xxxxxxx
PUSHER_APP_KEY=xxxxxxxxxxxxxxxxxxxx
PUSHER_APP_SECRET=xxxxxxxxxxxxxxxxxxxx
PUSHER_APP_CLUSTER=xxx

 

write your original credentials instead of xxxxxs

 

12. now add new model, migration for messages

php artisan make:model Messsage -m

 

13. now open User.php and code

 

<?php

namespace App;

use App\Message;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

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

    public function messages(){
        return $this->hasMany(Message::class,'sender_id');
    }
}

 

14. open Message.php and code

 

<?php

namespace App;
use App\User;

use Illuminate\Database\Eloquent\Model;

class Message extends Model
{
    protected $fillable = ['message'];

    public function user(){
        return $this->belongsTo(User);
    }
}

 

15. open message migration

 

<?php

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

class CreateMessagesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('messages', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('sender_id')->unsigned();
            $table->text('rec_id');
            $table->text('message');
            $table->timestamps();
        });
    }

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

 

16. now open user migration

 

<?php

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

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

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

 

17. now create event file named 'MessageSent'

 

php artisan make:event MessageSent

 

18. open MessageSent.php file and code

 

<?php

namespace App\Events;

use App\User;
use App\Message;
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;

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

    public $sender_id;
    public $rec_id;
    public $message;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($sender_id,$rec_id,$msg)
    {
        $this->sender_id = $sender_id;
        $this->rec_id = $rec_id;
        $this->message = $msg;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return[
            new PrivateChannel('chat-'.$this->sender_id),
            new PrivateChannel('chat-'.$this->rec_id)
        ];
    }

    
}

 

19. Now the backend tasks are over, lets move to the frontend as discussed above we will make it in React.

So move to resources/js/app.js file and code

require('./bootstrap');
import Chatpanel from './components/chatpanel';
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(, document.getElementById('chat_panel_container'));

 

20. Now in components folder create a new file and name it chatpanel.js and code

import React from 'react';
import './styles.css';

class Chatpanel extends React.Component {

    constructor(props){
        super(props);
        this.state = {
        msg_list:[],
        user_list:[],
            active_user:[]
        };
        this.handleEve = this.handleEve.bind(this);
        this.subscribeToPusher = this.subscribeToPusher.bind(this);
        this.loadUsers = this.loadUsers.bind(this);
        this.loadChats = this.loadChats.bind(this);
        this.getActiveUser = this.getActiveUser.bind(this);   
    }

    componentDidMount(){
        this.loadUsers();
        this.subscribeToPusher();  
    }

    getActiveUser(){
        if(this.state.active_user.length == 0){
            return;
        }
        else{
            return this.state.active_user[0];
        }
    }

    loadUsers(){
        let tok = document.querySelector('meta[name="csrf-token"]').content;
        fetch('http://127.0.0.1:8000/fetchUsers',{
            method:'POST',
            headers:{
                'Content-Type':'application/json',
                X_CSRF_TOKEN:tok,
                'Accept':'application/json'
            }
        })
        .then(response => response.json())
        .then(dat => {
            let arr = [];
            for(var x=0;x<dat.length;x++){
                arr.push(dat[x]);
            }
            this.setState({user_list:this.state.user_list.concat(arr)});
        })
        .catch((error) => {
            console.error(error);
        });
    }

    loadChats(el_id){
        let clicked_user_id = el_id.target.id;
        clicked_user_id = clicked_user_id.substr(5,clicked_user_id.length);


        for(var eu=0;eu<this.state.user_list.length;eu++){
            if(this.state.user_list[eu].id == clicked_user_id){
                this.setState({active_user:this.state.active_user.splice(0,this.state.active_user.length)});
                this.setState({active_user:this.state.active_user.concat(this.state.user_list[eu])});
                break;
            }
        }
        let tok = document.querySelector('meta[name="csrf-token"]').content;
    
        fetch('http://127.0.0.1:8000/fetchmessages?rec_id='+clicked_user_id,{
            method:'POST',
            headers:{
                'Content-Type':'application/json',
                X_CSRF_TOKEN:tok,
                'Accept':'application/json'
            }
        })
        .then(response => response.json())
        .then(dat => {
            this.setState({
                //activeUser:this.state.activeUser.push(this.state.user_list[clicked_user_id
            });
            console.log(JSON.stringify(dat));
            let arr = [];
            for(var x=0;x<dat.length;x++){
                arr.push(dat[x]);   
            }
            this.setState({msg_list:[]});
            this.setState({
                msg_list:this.state.msg_list.concat(arr)
            });
        })
        .catch((error) => {
            console.error(error);
        });
    }
 

    handleEve(e){
        let msg = document.getElementById('chat_tbox').value;
        let tok = document.querySelector('meta[name="csrf-token"]').content;
        let activeUserId = this.state.active_user[0].id;

        fetch('http://127.0.0.1:8000/messages?message='+msg+'&rec_id='+activeUserId,{
            method: 'POST',
            headers: {
                'Content-Type':'application/json',
                'X-CSRF-TOKEN':tok,
                'Accept':'application/json'
            }
        })
        .then(response => response.json())
        .then(dat => {
            console.log('from handleve : '+JSON.stringify(dat));
        })
        .catch((error) => {
            console.error(error);
        });
    }

    subscribeToPusher(){
        var new_msg = [];
        var channel = pusher.subscribe('private-chat-'+user.id);
        channel.bind('App\\Events\\MessageEvent',(d) => {
            
            //checking sent message from sender side
            if(d.sender_id == user.id){
                if(this.state.active_user[0].id == d.rec_id){
                    //alert('you have sent message to this user.');
                    this.setState({msg_list:this.state.msg_list.concat(d)});
                }
            }
            
            //checking message has been received or not
            if(d.sender_id != user.id){
                if(this.state.active_user.length != 0){
                    if(this.state.active_user[0].id == d.sender_id){
                        //alert('you have sent message to this user.');
                        this.setState({msg_list:this.state.msg_list.concat(d)});
                    }
                    else{
                        var id_to_notify = document.getElementById('user_'+d.sender_id);
                    }
                }
                else{
                    alert('no active user, you got a new message : '+d.message);
                }
            }
        });    
    }

    render(){
        let isAnyUserActive=false;
        if(this.state.active_user.length != 0){
            isAnyUserActive=true;
        }
        return (
            <div className=container>                
                <div className='row no-gutters'>
                    <div className='col-3'>
                        <div className='card'>
                            <div className='card-header'>
                                card header
                            </div>
                            <div className='card-body'>
                                <ul id='user_list' className='user_list list-group'>
                                    {this.state.user_list.map((number) =>
                                        <a href='#'>
                                            <li id={'user_'+number.id} onClick={this.loadChats} className='list-group-item d-flex justify-content-between align-items-center' key={'user_'+number.id}>
                                                {number.name}
                                                <span className='badge badge-primary badge-pill'>14</span>
                                            </li>
                                        </a>
                                    )}
                                </ul>
                            </div>              
                        </div>
                    </div>
                    <div className='col'>
                        <div className='card'>
                            <div className='card-header'>
                                {isAnyUserActive?this.state.active_user[0].name:'no active'}
                            </div>
                            <div className='card-body'>
                                <ul id='chat_list' className='chat_list'>
                                    {this.state.msg_list.map((msgs) =>
                                        (msgs.sender_id==user.id)?  
                                        <div className='sent' id={msgs.id} key={msgs.id}>{msgs.message}</div>                
                                        :
                                        <div className='replies' id={msgs.id} key={msgs.id}>{msgs.message}</div>
                                    )}
                                </ul>
                            </div>
                            <div className='card-footer'>
                                <input type='text' id='chat_tbox' className='form-control' placeholder='Enter message...' />
                                <input type='submit' className='btn btn-primary btn-sm' value='GO' onClick={this.handleEve} />
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}
export default Chatpanel;

21. now create new file name it styles.css and code

.user_list, .chat_list{
  padding:0;
  height:500px;
  overflow-y:scroll;
  overflow-x: hidden;
  list-style-type: none;
}

::-webkit-scrollbar {
  width: 10px;
}
  
/* Track */
::-webkit-scrollbar-track {
  background: #f1f1f1;
}

/* Handle */
::-webkit-scrollbar-thumb {
  background: #888;
}

/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
  background: #555;
}

.sent{
  position: relative;
  background-color: rgb(2, 133, 173);
  color: #f1f1f1;
  left:0;
  margin:10px;
  width:300px;
  border-radius: 10px;
}
.replies{  
  width:300px;
  background-color: #f1f1f196;
  color: rgb(2, 133, 173);
  position: relative;
  right:0;
  margin: 10px 10px 10px 60%;
  border-radius: 10px;
}

 

22. now in home.blade.php file and code

@extends('layouts.app')
@section('content')
   <script>
        let a_tok = document.querySelector('meta[name="csrf-token"]').content;
        
        //suscribing to pusher channel
        Pusher.logToConsole = true;
        var pusher = new Pusher('649f5ddeef4b7a77a1f3', {
            cluster: 'ap2',
            authEndpoint:'/broadcasting/auth',
            auth:{
                headers:{
                    'X-CSRF-TOKEN':a_tok
                }
            }
        });
    </script>
    
    <div class="container">
        <div class="row">
            <div class="col-md">
                <div id="chat_panel_container"> </div>
           </div>
        </div>
        <div class="row">
            <div class="col-md">
                <div id="chat_submit_container"></div>
            </div>
        </div>>
    </div>    
@endsection

 

 

After this run command

1. php artisan migrate:fresh

2. php artisan serve

3. npm run dev or watch or prod

For any further reference, feel free to check repository here




Sign in for comment. Sign in