Simple Chat App Using Laravel-Echo, Redis And Socket.io

Author: Filip Lekić
Posted on 16 April, 2018
Basics on how to use WebSocket protocol with Laravel-Echo and Redis with socket.io server in order to create real time chat app

Idea behind this blog post is creation of real-time chat application using WebSockets, PHP and Vue which will allow users to chat anonymously.

WebSocket is a computer communication protocol, providing full-duplex communication channels. Basically it allows bi-directional communication between user and server without reloading the page or client polling the server for the changes that needs to be fetched.

The application we will be creating in this blog post is just example minimum viable product and is not production ready. In order to make it production ready you will have to make a few changes regarding security and usability. I am not responsible in any way, use it at your own discretion. God speed :)

Requirements:

  • Laravel 5.5
  • Redis-server 3.0.6
  • MySQL 14.14
  • node.js 8.9.4

I suppose that it will work with next releases of above mentioned software, but for the sake of stability use exact versions.

I will also assume that you have basic familiarity with Laravel and Vue frameworks and the convenient tooling they provide.

Now let's start. We will generate new Laravel project and name it simple-chat-app. Then we will create database inside of our favorite database-client i.e. (Sequelpro, DBeaver or even the good old mysql-cli) and up creation we will enter into root of application and search for .env file. If there is no .env, rename env.example to .env and open it with your favorite text editor or IDE. Enter the name of recently created database to DB_DATABASE, username to DB_USERNAME, password to DB_PASSWORD. And while you are here change BROADCAST_DRIVER to redis.

So next step will be creation of migration and model for the message records that will contain data we'll be broadcasting to the users. We can do that by running artisan command:

php artisan make:model -m Message

Let's modify generated migration file.

File: simple-chat-app/database/migrations/2018_04_20_100001_create_messages_table.php
// ...
public function up()
{
    Schema::create('messages', function (Blueprint $table) {
        $table->increments('id');
        $table->text('content');
        $table->timestamps();
    });
}
// ...

Now we should migrate database with following command:

php artisan migrate

Then let's go back to PHP and create two routes - one for fetching all the messages from database and another for posting them. To accomplish that task we will edit web.php file.

File: simple-chat-app/routes/web.php
use App\Message;

// Return index page with the Vue component we will crete soon
Route::get('/', function () {
    return view('welcome');
});

// Return all messages that will populate our chat messages
Route::get('/getAll', function () {
    $messages = Message::take(200)->pluck('content');
    return $messages;
});

// Allows us to post new message
Route::post('/post', function () {
    $message = new Message();
    $content = request('message');
    $message->content = $content;
    $message->save();
    return $content;
});

Let's modify default welcome.blade.php template to include Vue component that will act as a chat box. We'll remove all but bare minimum required for our application to run and we will add <chat-box> component tag.

File: simple-chat-app/resources/views/welcome.blade.php
<!doctype html>
<html lang="{{ app()->getLocale() }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <meta name="csrf-token" content="{{ csrf_token() }}">

        <title>Anonymous Chat App</title>

        {{-- Script hosted on running laravel-echo-server --}}
        <script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>
    </head>
    <body>
        <div class="content">
            <div id="app">
                <chat-box></chat-box>
            </div>
        </div>
        <script src="{{ mix('js/app.js') }}"></script>
    </body>
</html>

Now we will have to create chat component. We can do it by renaming ExampleComponent.vue located inside of the components directory.

File: simple-chat-app/resources/assets/js/components/ChatBox.vue
<template>
    <div>
        <p v-for="message in messages">{{ message }}</p>
        <input v-model="text">
        <button @click="postMessage" :disabled="!contentExists">submit</button>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                text: '',
                messages: []
            }
        },
        computed: {
            contentExists() {
                return this.text.length > 0;
            }
        },
        methods: {
            postMessage() {
                axios.post('/post', {message: this.text}).then(({data}) => {
                    this.messages.push(data);
                    this.text = '';
                });
            }
        },
        created() {
            axios.get('/getAll').then(({data}) => {
                this.messages = data;
            });
        }
    }
</script>

As you can see chat box consists of paragraphs that are created from messages array we fetched on create life hook of the component. postMessage method straight forwardly sends data from text variable which is two-way binded with input filed inside of template tags and on success it clears the value of the textand populate messages array.

At the end we will have to register it inside of app.js file

File: simple-chat-app/resources/assets/js/app.js
// ...
Vue.component('chat-box', require('./components/ChatBox.vue'));
// ...

For the fronend we will install default npm packages Laravel provided us when we initialized the project. We will use them to build Vue component. Inside the shell run:

npm install

We will use Redis as message broadcaster. So we'll install, as official documentation says, predis package. Inside of your terminal:

composer require predis/predis

To enable broadcasting from our backend app we will have to uncomment the line containing BroadcastServiceProvider::class from app.php.

File: simple-chat-app/config/app.php
// ...
/*
 * Application Service Providers...
 */
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// Line below should be uncomment
App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
// ...

Next we will create an event that will broadcast message to the clients connected with the socket.

File: simple-chat-app/app/Events/MessageSent.php
namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

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

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public $message;

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

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('public');
    }
}

We made few changes on our generated Event by implementing interface ShouldBroadcast. Then added public property $message which will hold our message. Also we changed PrivateChannel to Channel inside of broadcastOn method . Private and Presence channels are beyond the scope of this blog post.

Laravel-Echo is a npm package that makes it painless to subscribe to channels and listen for events. We should install it by running following command:

npm install laravel-echo

Next we will need to install socket.io server. There is great community package tlaverdure/laravel-echo-server. We will install it globally using command

npm install -g laravel-echo-server

Now, as documentations says, let's run following command inside of the root of our application:

laravel-echo-server init

You will be prompted by the init script, answer as following:

? Do you want to run this server in development mode? Yes           
? Which port would you like to serve from? 6001                     
? Which database would you like to use to store presence channel members? redis                                                          
? Enter the host of your Laravel authentication server. http://localhost                                                                 
? Will you be serving on http or https? http                        
? Do you want to generate a client ID/Key for HTTP API? Yes         
? Do you want to setup cross domain access to the API? No

Prompt will generate laravel-echo-server.json file that we can change later to suit our needs.

Following we should run the server from project root directory:

laravel-echo-server start
In order to keep application running you will have to leave socket.io server running all the time.

We will have to make few changes in bootstrap.js.

File: simple-chat-app/resources/js/bootstrap.js
// ...
import Echo from 'laravel-echo'

// window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001'
});

We just imported and binded Echo object to the window. Next step is to refactor ChatBox.vue to act on received event.

File: simple-chat-app/resources/assets/js/components/ChatBox.vue
// ...
created() {
    axios.get('/getAll').then(({data}) => {
        this.messages = data;
    });
    // Registered client on public channel to listen to MessageSent event
    Echo.channel('public').listen('MessageSent', ({message}) => {
        this.messages.push(message);
    });
}
// ...

Finally we will have to broadcast event from our backed server we will do that by tweaking web.php route.

File: simple-chat-app/routes/web.php
// ...
Route::post('/post', function () {
    $message = new Message();
    $content = request('message');
    $message->content = $content;
    $message->save();

    event(new MessageSent($content));

    return $content;
// ...

We included MessageSent event, and we told the router to emit it when user post message.

Also, don't forget to build npm modules and Vue component, from you terminal emulator run:

npm run dev

Now you can chat on public channel with your peers, yeey :).

In order to improve this chat and make it production ready you should have to validate post messages, secure databases, both Redis and MySQL and use SSL certificates for messages being sent by Socket.io servers.

Related documentation: