DEV Community

Chimezie Enyinnaya
Chimezie Enyinnaya

Posted on

How to build a ‘who’s typing’ feature with Laravel and Pusher

When you're using a chat app, having a 'who's typing' feature really improves the user experience and makes it easier for users to interact with the application. If you're chatting with someone, you can easily see when the other person is responding to your chats, saving you time from typing 'you there?' and let you know when to wait for the other person's response, so you don't keep sending messages.

In this tutorial, I will show you how to build a 'who's typing' feature with Laravel and Pusher, using the concept of a chat app to demonstrate it. Note that this tutorial focuses on the 'who's typing' part of the app (if you want to learn how to build a chat app using Laravel and Pusher, you should read this tutorial)

Let's take a quick look at what we'll be building:

The code of the completed demo is available on GitHub.

Setting Up Laravel

Create a new Laravel project. (I prefer using the Laravel installer) Open your terminal and run the code below:

laravel new laravel-whos-typing
Enter fullscreen mode Exit fullscreen mode

Next, we need to setup our new Laravel project. First, we need to register the App\Providers\BroadcastServiceProvider. Open config/app.php and uncomment App\Providers\BroadcastServiceProvider in the providers array.

We then need to tell Laravel that we are using the Pusher driver in the .env file:

// .env

BROADCAST_DRIVER=pusher
Enter fullscreen mode Exit fullscreen mode

Since we specified we want to use Pusher as our broadcasting driver, we need to install the Pusher PHP SDK:

composer require pusher/pusher-php-server
Enter fullscreen mode Exit fullscreen mode

Setting Up Pusher

If you don't have one already, create a free Pusher account at https://pusher.com/signup then log in to your dashboard and create an app. Take note of your app credentials as we'll be using them shortly.

Now, let's fill in our Pusher app credentials. If you open the config/broadcasting.php, you'll notice that Laravel is pulling some of the Pusher credentials from the .env file. So let's update the .env file to contain our Pusher app credentials:

// .env

PUSHER_APP_ID=xxxxxx
PUSHER_APP_KEY=xxxxxxxxxxxxxxxxxxxx
PUSHER_APP_SECRET=xxxxxxxxxxxxxxxxxxxx
Enter fullscreen mode Exit fullscreen mode

Remember to replace the xs with your Pusher app credentials. You can find your app credentials under the Keys section on the Overview tab in the Pusher Dashboard.

Also, remember to fill in the cluster of your Pusher app and other additional options:

// config/broadcasting.php

'options' => [
   'cluster' => 'eu',
   'encrypted' => true
],
Enter fullscreen mode Exit fullscreen mode

Installing Frontend Dependencies

For this tutorial, we'll be using Bootstrap, Vue and Axios, which have been setup for us by Laravel, though we still need to install each of the dependencies. To compile our CSS and JavaScript, we need to install Laravel Mix, which is a wrapper around Webpack. We can install these dependencies through NPM:

npm install
Enter fullscreen mode Exit fullscreen mode

We also need to install Laravel Echo, which is a JavaScript library that makes it painless to subscribe to channels and listen for events broadcast by Laravel and of course the Pusher JavaScript library:

npm install --save laravel-echo pusher-js
Enter fullscreen mode Exit fullscreen mode

Once installed, we need to tell Laravel Echo to use Pusher. At the bottom of the resources/assets/js/bootstrap.js file, uncomment the Laravel Echo section and update the details with:

// resources/assets/js/bootstrap.js

import Echo from "laravel-echo"

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: Laravel.pusherKey,
    cluster: 'eu',
    encrypted: true
}); 
Enter fullscreen mode Exit fullscreen mode

Since we specified our Pusher app credentials in the .env file, notice Laravel.pusherKey from the code above, we'll load our Pusher app key from the config instead of hard coding it directly in resources/assets/js/bootstrap.js. We'll define Laravel.pusherKey in a later section.

Also use the same cluster that you specified earlier in config/broadcasting.php.

With the setups done, let's start implementing a 'who's typing' feature in our Laravel application.

To do this, we need to know when a user is typing into the chat box. There are different ways to accomplish this, but in this tutorial, we'll add event listeners to the chat box. With this, we can know when a user is actually typing a message and display the typing indicator to the appropriate users.

We'll make use of JavaScript keyboard events: onkeydown which fires event when a user is pressing a key. This means that when a user presses a key, we send events to Pusher. These types of events are called client events and do not hit the server at all. But how do we broadcast such events to Pusher since they are a bit different from the normal events (server to Pusher events)? Well, Pusher is aware of such events and has a special way of handling them.

By default, when you create a Pusher app, client events are not enabled. We have to enable this for our app. To enable client events in your Pusher app, select the app then click on the App Settings tab and then check the box next to Enable client events.

Once we have enabled client events in our Pusher app, we can now trigger and listen for client events in our chat app.

Authenticating Users

Our chat app will require users to be logged in before they can begin to chat. So, we need an authentication system:

php artisan make:auth
Enter fullscreen mode Exit fullscreen mode

This will create the necessary routes, views and controllers needed for an authentication system.

Before we go on to create users, we need to run the users migration that comes with a fresh installation of Laravel. But to do this, we first need to setup our database. Open the .env file and enter your database details:

// .env

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel-chat
DB_USERNAME=root
DB_PASSWORD=root
Enter fullscreen mode Exit fullscreen mode

Update with your own database details. Now, we can run our migration:

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Note: There's a bug in Laravel 5.4 if you're running a version of MySQL older than 5.7.7 or MariaDB older than 10.2.2. More info here. This can be fixed by replacing the boot() of app/Providers/AppServiceProvider.php with:

// app/Providers/AppServiceProvider.php

// remember to use
Illuminate\Support\Facades\Schema;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
  Schema::defaultStringLength(191);
}
Enter fullscreen mode Exit fullscreen mode

Defining App Routes

Open routes/web.php and replace the routes with the code below:

// routes/web.php

Auth::routes();

Route::get('/', function () {
    return view('chat');
})->middleware('auth');
Enter fullscreen mode Exit fullscreen mode

The routes are pretty simple: a route that will handle authentication and a route to the homepage that will render a chat view which we'll create shortly.

NOTE: Since we have removed the /home route, you might want to update the redirectTo property of both app/Http/Controllers/Auth/LoginController.php and app/Http/Controllers/Auth/RegisterController.php to:

protected $redirectTo = '/';
Enter fullscreen mode Exit fullscreen mode

Creating The Chat App View

Create a new resources/views/chat.blade.php file and paste into it:

// resources/views/chat.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Chats</div>

                <div class="panel-body">
                    <ul>
                        <li v-for="message in messages">
                            @{{ message.user.name }} - @{{ message.message }}
                        </li>
                    </ul>
                    <div>
                        <div class="input-group">
                            <input type="text" name="message" class="form-control" placeholder="Type your message here..." v-model="newMessage" @keyup.enter="sendMessage">
                            <span class="input-group-btn">
                                <button class="btn btn-primary" @click="sendMessage">
                                    Send
                                </button>
                            </span>
                        </div>
                        <span v-show="typing" class="help-block" style="font-style: italic;">
                            @{{ user }} is typing...
                        </span>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
Enter fullscreen mode Exit fullscreen mode

Once again pretty straightforward, we are using Vue here. We loop through each of the messages and display them. Next, there is a input field and a send button for composing chat messages. The input field is binded to the newMessage data. When the send button is clicked or the enter key is pressed on the input field, a sendMessage() is called. Lastly, there is span holding the 'is typing' indicator. This will be hidden by default and will be displayed using Vue's v-show when typing is true (that is when a user is typing).

Notice that we are displaying the name of the user along with the 'is typing' indicator, we need a way to pass the authenticated user to our JavaScript file. Remember from resources/assets/js/bootstrap.js, where we used Laravel.pusherKey, we also need to pass Pusher app key to our JavaScript file. We can do this by updating the `section inresources/views/layouts/app.blade.php(which was created when we ranmake:auth`) with:

` language-js
resources/views/layouts/app.blade.php

window.Laravel = {!! json_encode([
'csrfToken' => csrf_token(),
'user' => Auth::user(),
'pusherKey' => config('broadcasting.connections.pusher.key'),
]) !!};


The code above creates a
Laravelobject on the global window, we then add some items to the object. Theuseritem will be the currently authenticated user and thepusherKeyitem will load our Pusher app key from theconfig/broadcasting.php` config file.

Laravel has integrated Vue and Axios for us, so we can start using Vue without any further setup. There is an app.jsfile that Laravel creates by default within resources/assets/js/app.js. Open this file and update with the code below:

` language-js
// resources/assets/js/app.js

require('./bootstrap');

const app = new Vue({
el: '#app',

data: {
    messages: [],
    newMessage: '',
    user: '',
    typing: false
},

methods: {
    sendMessage() {
        // add new message to messages array
        this.messages.push({
            user: Laravel.user,
            message: this.newMessage
        });

        // clear input field
        this.newMessage = '';

        // persist to database
    }
}
Enter fullscreen mode Exit fullscreen mode

});

First, we require the
resources/assets/js/bootsrap.jsfile that contains our package setups and integrations. Next, we create a new Vue instance and bind it to **app**id. We then create some data variables:messagesarray will hold our chat messages,newMessagewill be the new message that a user sends,userwill be the currently authenticated user and finallytypingwill holdtrueorfalse` indicating whether a user is typing or not.

Next we define a sendMessage() that simply adds the new message along with the user that sent it to the messages array and finally clears the input field. (If you are building an actual chat app, you might want to do an AJAX request to persist the new message to the database here.)

Having done this, we can start sending messages and our messages will be displayed on the chat view. Now let's move on to the meat of this tutorial; adding 'who's typing' to our Laravel application.

Laravel Echo provides some handy methods to integrate with client events which we'll be using to implement our 'who's typing' indicator.

Paste the code below into the resources/assets/js/app.js within the methods object:

` language-js
// resources/assets/js/app.js

isTyping() {
let channel = Echo.private('chat');

setTimeout(function() {
channel.whisper('typing', {
user: Laravel.user,
typing: true
});
}, 300);
},

The
isTyping()will be triggered when an **onkeydown** event is fired within the chat input field (that is, when a user is typing a message). First, we subscribe to a private channel calledchatand trigger a client event using Laravel Echo'swhisper()after 0.3s. Thewhisper()accepts the name of the client event, in our casetypingand the data we want to broadcast. Since Pusher specifies that client events must be prefixed byclient-, Laravel is smart enough to prefix theclient-for us under the hood. We pass the user that is typing a message and typing astrue` as the data we are broadcasting.

Note: Client events can only be triggered on private and presence channels because they require authentication. Also, client events are not delivered to the originator of the event. For more information on client events, kindly checkout the Pusher documentation.

Since we created a private channel, only authenticated users will be able to listen on the chat channel. We need a way to authorize that the currently authenticated user can actually listen on the channel. This can be done by in the routes/channels.php file:

` language-php
// routes/channels.php

Broadcast::channel('chat', function ($user) {
return Auth::check();
});

We pass to the
channel(),the name of our channel and a callback function that will either returntrueorfalse` depending on whether the current user is authenticated.

Now that we can trigger client events, we also need a way to listen for the client events in our application. To do this, add the code below to resources/assets/js/app.js just after the data object:

` language-js
// resources/assets/js/app.js

created() {
let _this = this;

Echo.private('chat')
.listenForWhisper('typing', (e) => {
this.user = e.user;
this.typing = e.typing;

  // remove is typing indicator after 0.9s
  setTimeout(function() {
    _this.typing = false
  }, 900);
});
Enter fullscreen mode Exit fullscreen mode

},

Again we subscribe to the
chatchannel. To listen for client events, we use thelistenForWhisper()and set both theuserandtyping` data accordingly. Lastly, we remove the is typing indicator after 0.9s of a user not typing.

Before we start testing the who's typing feature, let update the input field of the chat view with:

` language-html
// resources/view/chat.blade.php



We added two keyboard events:
@keydownand@keyupwhich are Vue equivalent of the JavaScript keyboard events we talked about earlier. On keydown, theisTyping()will be triggered and on keyup, thenotyping()` will be triggered.

We can now compile the JavaScript files using Laravel Mix using:

` language-bash
npm run dev
`

Now we can start our chat app by running:

` language-bash
php artisan serve
`

Conclusion

With such a basic app, we have seen how to implement a 'who's typing' feature in a Laravel application using Pusher. I hope you find this tutorial helpful and if you encounter any problems following this tutorial, kindly drop a comment below and we'd tackle them together.

This post was originally posted by the author on the Pusher blog.

Top comments (0)