Tutorial: Build Customer Support Chat with Laravel, Vue and Stream

6 min read

Quick and active customer service is an integral part of any company or organization, especially those that conduct most of their business online. Quality customer service goes beyond merely fielding comments and questions from customers. The types of interactions your team has with your clients have a direct impact on how your customers perceive, use, and pay for your service.

Chimezie E.
Chimezie E.
Published September 26, 2019 Updated October 26, 2020

Quick and active customer service is an integral part of any company or organization, especially those that conduct most of their business online. Quality customer service goes beyond merely fielding comments and questions from customers. The types of interactions your team has with your clients have a direct impact on how your customers perceive, use, and pay for your service.

In this tutorial, we'll build a live chat using Laravel, Vue.js and Stream Chat.

Laravel is a free, open-source PHP web framework, and was created for the development of web applications following the model-view-controller (MVC) architectural pattern and based on Symfony. It offers a lot of great features and presets that make developing backend applications easier and better.

Vue.js is a lightweight front-end framework used for building web applications. Laravel also supports it out of the box.

Stream Chat is an API for building scalable, enterprise-grade chat applications. The service removes all of the hassles of creating such sophisticated features from you and lets you focus on getting your service up and running quickly.

Prerequisites

To follow this tutorial, a basic understanding of Laravel, Vue.js, and JavaScript are required. To build the required application, here are a few tools we will use:

Getting Started

To get started, we'll be using the Laravel installer to create a new Laravel application. If you don't already have the Laravel installer setup on your computer, you can follow this installation guide. Run the following command to create a new Laravel application:

laravel new stream-laravel-vue-livechat && cd stream-laravel-vue-livechat

Once the application is created, we need to install the NPM dependencies (because Vue.js comes pre-packed as an NPM dependency). In your terminal type:

npm install

For this tutorial, we'll be concerned with the resources/js directory, which is where Vue.js will be instantiated from.

Now, we can start creating our application.

1. Installing the Stream Chat PHP Client

To start working with Stream Chat and Laravel, we need to install the Stream Chat PHP Client. For that, run the following command in your terminal:

composer require get-stream/stream-chat

2. Installing the Stream Chat JavaScript Client

To be able to use Stream Chat on our Vue.js front-end, we need to install the JavaScript client. Run the following in your terminal:

npm install stream-chat

Once that is done, we need to configure our app to work with it. To start working with Stream Chat and Laravel, we need our API key.

Create an account on Stream Chat if you don't have one already. On your dashboard, create a new app by clicking the create app button.

Laravel

Once that is done, you should see an API Key, API Secret and App ID (at the top) of your newly created app under the chat tab of the navigation.

Account Credentials

Copy them and add them to your .env file in your Laravel project. To be able to access these environment variables in our front-end, we need to prefix them with MIX_:

MIX_STREAM_API_KEY=YOUR_STREAM_KEY
MIX_STREAM_API_SECRET=YOUR_STREAM_SECRET

3. Creating the Base View and Controller

Since we already ran the npm install command when we installed Laravel, we don't need to define it anymore. Instead, we need to create a base content that will be displayed by Laravel. Think of the default welcome view but this time done with Vue.js.

Inside the resources/js directory, add the following code to app.js:

require('./bootstrap');
import "../sass/chat.sass";

window.Vue = require('vue');
window.axios = require('axios');
window.VueRouter=require('vue-router').default;

const Chat = Vue.component('chat', require('./components/Chat.vue').default);
const Admin = Vue.component('admin', require('./components/AdminChat.vue').default);

Vue.prototype.$baseurl = "https://localhost:8000";

window.router = new VueRouter({
  routes:[
      {
        path: '/',
        name: 'chat',
        component: Chat,
      },
       {
        path: '/admin',
        name: 'admin',
        component: Admin,
      }
  ],
})

const app = new Vue({
    router,
    el: '#app',
});

Here, we instantiated our components and defined the routes for our application. Since we have not created the components yet, our app will throw an undefined error. Let's create them.

To differentiate between our chat screens (Admin & User), we'll create the two components: Chat and AdminChat inside resources/js/components.

Create resources/js/components/Chat.vue and add the following code to it:

// resources/js/components/Chat.vue

<template>
    <div id="chat">
        <button class="btn btn-primary c-chat-widget-button" ref="button" @click.prevent="toggleModal()">C</button>
        <div class="c-chat-widget" ref="modal" :class="{show: modal.show}">
            <div class="c-chat-widget-dialog">
                <div class="c-chat-widget-content">
                    <div class="c-chat-widget-header">Chat With Us Admin</div>
                    <div class="c-chat-widget-body">
                        <div class="c-chat-widget-bubble c-chat-widget-bubble-left row" v-for="msg in messageData">
                            <div class="c-chat-widget-bubble-icon">{{msg.user.id}}</div>
                            <div class="c-chat-widget-bubble-text">
                              {{msg.text}}
                            </div>
                        </div>
                    </div>
                    <div class="c-chat-widget-footer">
                        <form @submit.prevent="sendmessage">
                            <textarea name="" id="" cols="30" v-model="message" rows="10" class="c-chat-widget-text" placeholder="Enter Text Here"></textarea>
                        <button class="btn btn-block btn-success">Send Message</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                modal: {
                    show: false,
                },
                message: '',
                messageData: []
            }
        },

        mounted() {
        },
        methods: {
            toggleModal() {
                this.modal.show = !this.modal.show;
            },
            showModal() {
                this.modal.show = true;
            },
            hideModal() {
                this.modal.show = false;
            },
    }
</script>

Next, create resources/js/components/AdminChat.vue and add the following code to it:

Building your own app? Get early access to our Livestream or Video Calling API and launch in days!
// resources/js/components/AdminChat.vue

<template>
    <div id="chat">
        <button class="btn btn-primary c-chat-widget-button" ref="button" @click.prevent="toggleModal()">C</button>
        <div class="c-chat-widget" ref="modal" :class="{show: modal.show}">
            <div class="c-chat-widget-dialog">
                <div class="c-chat-widget-content">
                    <div class="c-chat-widget-header">Chat With Us Admin</div>
                    <div class="c-chat-widget-body">
                        <div class="c-chat-widget-bubble c-chat-widget-bubble-left row" v-for="msg in messageData">
                            <div class="c-chat-widget-bubble-icon">{{msg.name.id}}</div>
                            <div class="c-chat-widget-bubble-text">
                               {{msg.text}}
                            </div>
                        </div>
                    </div>
                    <div class="c-chat-widget-footer">
                        <form @submit.prevent="sendmessage">
                            <textarea name="" id="" cols="30" v-model="message" rows="10" class="c-chat-widget-text" placeholder="Enter Text Here"></textarea>
                        <button class="btn btn-block btn-success">Send Message</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                modal: {
                    show: false,
                },
                message: '',
                messageData: []
            }
        },
        mounted() {
        },
        methods: {
            toggleModal() {
                this.modal.show = !this.modal.show;
            },
            showModal() {
                this.modal.show = true;
            },
            hideModal() {
                this.modal.show = false;
            },
    }
</script>

Next, let's create the chat.sass file inside the resources/sass directory and add the following to it. This will serve as the necessary styling for our application:

// resources/sass

#chat
    font-family: arial
    .c-chat
        &-widget
            bottom: calc(50px + 30px + 50px)
            display: block
            opacity: 0
            position: fixed
            right: 50px
            transform: translate(100%, 100%)
            transition: .5s
            z-index: 5000
            &.show
                opacity: 1
                transform: translate(0)
            &-content
                background: #eee
                box-shadow: 0 10px 30px rgba(#000, .2)
                border-radius: 3px
                max-width: 100%
                overflow: hidden
                width: 400px
            &-header
                background: #007bff
                color: #fff
                padding: 10px 15px
            &-body
                padding: 10px 15px
            &-footer
                padding: 10px 15px
            &-button
                border-radius: 999px
                bottom: 50px
                cursor: pointer
                height: 50px
                line-height: 50px
                padding: 0
                position: fixed
                right: 50px
                text-align: center
                width: 50px
                z-index: 5050
            &-text
                border: 1px solid #eee
                border-radius: 3px
                height: 100px
                outline: none !important
                padding: 10px 20px
                width: 100%
            &-bubble
                background: #fff
                border-radius: 3px
                box-shadow: 0 3px 10px rgba(#000, .1)
                margin-bottom: 15px
                max-width: 96%
                padding: 10px
                &-icon
                    border-radius: 3px
                    color: #aaa
                    display: inline-block
                    flex: 0 0 30px
                    font-size: 12px
                    font-weight: 900
                    margin-right: 10px
                    text-align: center
                    vertical-align: middle
                &-text
                    border-left: 2px solid #aaa
                    display: block
                    margin-top: 15px
                    padding: 10px 20px
                    vertical-align: middle

Next, in your views directory, create a layouts directory and create a file called app.blade.php and add the following to it. This will serve as the base layout file all components will inherit from:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
  <head>
     <meta charset="utf-8">
     <meta name="csrf-token" content="{{ csrf_token() }}">
  </head>
  <body>
    <div id="app">
        @yield("content")
    </div>
    <script src="/js/app.js"></script>
  </body>
</html>

Next, create chat.blade.php and add the following lines of code to it. This will serve as the main entry point for our application:

// views/chat.blade.php

@extends('layouts.app')

@section('content')
    <router-view></router-view>
@endsection

At this point, we are done with our views. Let's tell Laravel to render the application for us. For that update your routes to the following:

// routes/web.php

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

To see this in effect, we need to start our server by typing php artisan serve. Then visit http://localhost:8000 in your browser. Click on the icon in the bottom right hand corner and you'll see the "Chat With Us" dialog display.

Chat Example

Now, we have our views up and running we need to add functionality to send and receive messages.

Channels & Messages

Stream utilizes channels and events to determine who our message gets sent to. Every user belongs to a channel and receives all messages that get sent to that channel. For a live chat, we need a single channel as it is only a direct line from the user to the admin. For that reason, we'll not store our channels in a database we'll instead hardcode it.

Create a new controller using the command below:

php artisan make:controller MessagesController

Next, open it and add the following to it:

// controllers/MessagesController.php

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use GetStream\StreamChat\Client;
use App\Message;

class MessagesController extends Controller
{
    protected $client;

    public function __construct(){
        $this->client =  new Client(
            getenv("MIX_STREAM_API_KEY"), 
            getenv("MIX_STREAM_API_SECRET"),
        );
    }

    public function generateToken(Request $request){
        return response()->json([
            'token' => $this->client->createToken($request->input('name'))
        ], 200);
    }

    public function getChannel(Request $request){
        $from = $request->input('from');
        $to = $request->input('to');

        $from_username = $request->input('from_username');
        $to_username = $request->input('to_username');
 
        $channel_name = "livechat-{$from_username}-{$to_username}";

        $channel = $this->client->getChannel("messaging", $channel_name);
        $channel->create($from_username, [$to_username]);

        return response()->json([
            'channel' => $channel_name
        ], 200);
    }
}

Here, we define two methods and a constructor. We instantiate our Stream Chat in our constructor. Then we define two other methods:

  • generateToken: generates a token and returns it to the client as a response.
  • getChannel: creates a new channel then adds both users (admin/user) to it and returns the newly created channel name to the client.

To access these methods from our client, we need to create our routes. Open the routes/web.php file and add the following to it:

// routes/web.php

Route::post('generate-token', 'MessagesController@generateToken');
Route::post('get-channel', 'MessagesController@getChannel');

Now that our backend work is complete. Let's implement the functionality of our front-end to start chatting.

Client-side

To use the chat methods, we need to initialize the Stream Chat JavaScript client that will enable us to send, listen, and receive new messages.

We'll write the methods in our two components. First, open up your Chat.vue file and update it with the following code:

// js/components/Chat.vue

<script>
  import { StreamChat } from 'stream-chat';
    export default {
        data() {
            return {
                modal: {
                    show: false,
                },
                message: '',
                messageData: [],
                collapsed: false,
                channel: null
            }
        },
        computed: {
           username() {
                return "client"
           }
        },
        mounted() {
          this.initializeClient();
          this.createChannel();
        },
        methods: {
            async createChannel(){
            const {data} = await axios.post('/getChannel', {
                from_username: "client",
                to_username: "admin",
                from: "client",
                to: "admin",
            })
            const channel = this.client.channel('messaging', data.channel, {
                name: 'LiveChat channel',
                members: ["client", "admin"]
            });
            this.channel = channel
            channel.watch().then(state => {
                channel.on('message.new', event => {
                    this.messageData.push(event.message)
                });
             })
            },
            async initializeClient () {

            const {data} = await axios.post('/generate-token', {
                name: "client"
            })
            const client = new StreamChat(process.env.MIX_STREAM_API_KEY);
            await client.setUser(
                {
                    id: "client",
                    name: "client",
                },
                    data.token,
            );
               this.client = client
            },
            sendMessage() {
                this.channel && this.channel.sendMessage({
                    text: this.message
                });
                this.message = "";
            },
            toggleModal() {
                this.modal.show = !this.modal.show;
            },
            showModal() {
                this.modal.show = true;
            },
            hideModal() {
                this.modal.show = false;
            },
        }
    }
</script>

Let's go through our newly defined property and methods:

  • username: this is a computed property, which represents the username of the person chatting
  • createChannel: this method handles the creation of channels. It sends the relevant data to the channel API we created earlier and gets back the response. It then passes the relevant data to the channel instance on the client, then watches for changes and immediately updates the screen.
  • initializeClient: this method runs immediately, once the component loads. It sends a message to the token API we created earlier and gets a token. It then instantiates Stream Chat, passes the newly acquired token to it for verification, and finally returns the user.
  • sendMessage: this method handles the sending of messages back and forth the channel. Initially, it sets the message variable to an empty string. Then checks if the channel exists before trying to send it.

Next, open up your AdminChat.vue and update it with the following code:

// js/components/AdminChat.vue

<script>
  import { StreamChat } from 'stream-chat';
    export default {
        data() {
            return {
                modal: {
                    show: false,
                },
                message: '',
                messageData: [],
                collapsed: false,
                channel: null
            }
        },
        computed: {
        username() {
              return "admin"
           }
        },
        mounted() {
            this.initializeClient();
            this.createChannel();
        },
        methods: {
            async createChannel(){
            const {data} = await axios.post('/getChannel', {
                from_username: "admin",
                to_username: "client",
                from: "admin",
                to: "client",
            })
            const channel = this.client.channel('messaging', data.channel, {
                name: 'LiveChat channel',
                members: ["admin", "client"]
            });
            this.channel = channel
            channel.watch().then(state => {
                this.messages = state.messages
                channel.on('message.new', event => {
                    this.messageData.push(event.message)
                });
             })
            },
            async initializeClient () {

            const {data} = await axios.post('/generate-token', {
                name: "admin"
            })
            const client = new StreamChat(process.env.MIX_STREAM_API_KEY);
            await client.setUser(
                {
                    id: "admin",
                    name: "admin",
                },
                    data.token,
                );
                this.client = client
            },
            sendMessage() {
                this.channel && this.channel.sendMessage({
                    text: this.message
                });
                this.message = "";
            },
            toggleModal() {
                this.modal.show = !this.modal.show;
            },
            showModal() {
                this.modal.show = true;
            },
            hideModal() {
                this.modal.show = false;
            },

        }
    }
</script>

Now visit http://localhost:8000 on your browser to start chatting as a client and visit http://localhost:8000/#admin to respond to the messages.

Full Chat Example

Final Thoughts

In this tutorial, we have explored how to make a functional live chat using Laravel and Stream. The knowledge from here can be used to create more sophisticated conversation and real-time applications.

Stream offers a wide variety of features that can be useful in creating truly mature feed and chat applications. You can learn more about Stream Chat here.

The code from this tutorial is located on GitHub.

Integrating Video With Your App?
We've built a Video and Audio solution just for you. Check out our APIs and SDKs.
Learn more ->