Adding User Interactivity to a Chatroom with Laravel, Vue.js and Stream Chat

6 min read
Chimezie E.
Chimezie E.
Published November 15, 2019

In my last tutorial here, we created a chatroom application using Laravel, Vue.js, and Stream Chat. Although we only covered the basics of getting our chatroom up and running, Stream Chat offers a lot of other functionality.

In this tutorial, we will explore some of those functionalities, and we’ll be adding them to our chatroom application. We’d add the following functionality:

  • Add a leave channel functionality
  • Add functionality to join channel manually
  • Show a typing indicator when the user is typing
  • Display a notification when a user enters chat
  • Display a notification when a user leaves the chat

Adding the Leave Channel Functionality

Currently, in our application, users are added to the chatroom as soon as they register. But typically, in a chatroom application, users should have the ability to join or leave any channel of their choice explicitly.

To implement this functionality, we need to tell Stream Chat to remove or allow a user to join different channels as they want. Let’s see this in action.

Add the following code within the class definition of ChatController.php:

// app/Http/Controllers/ChatController.php

...
  protected $client;
  protected $channel;

  public function __construct()
  {
      $this->client = new StreamClient(env("MIX_STREAM_API_KEY"), env("MIX_STREAM_API_SECRET"), null, null, 9);
      $this->channel = $this->client->Channel("messaging", "chatroom");
  }

  public function leaveChannel(Request $request)
  {
     return $this->channel->removeMembers([$request->username]);
  }
...

We created a new leaveChannel method to handle removing users from a particular channel. This method calls the removeMembers function on the current channel instance, which gets set as soon as the controller is invoked. The removeMembers function accepts an array of user IDs (username in our case) we want to remove from the channel.

Still, inside the ChatController, update the generateToken method as below:

// app/Http/Controllers/ChatController.php

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

Next, we need to create the endpoint that will trigger the leave channel functionality. Add the code below inside routes/api.php:

// routes/api.php

Route::post('/leave-channel', 'ChatController@leaveChannel');

Now that all our backend needs are set up, we need to add a button on our frontend that will be used by users when they want to leave the channel. Inside the ChatRoom component, add the following code underneath the member's list:

// resources/js/ChatRoom.vue
// inside the template section

<button type="button" class="mt-4 btn btn-primary" @click="leaveChannel">Leave Channel</button>

Here, we add a simple button to allow users to leave the channel. On click of this button, the leaveChannel method will be triggered. Let’s create the method:

// resources/js/ChatRoom.vue
// inside the script section

leaveChannel() {
  axios.post("/api/leave-channel", {
    username: this.username,
  });

  window.location.href = '/dashboard';
}

This makes a POST request to our leavel-channel endpoint with the username of the user leaving the channel. Then redirect the user to the dashboard route.

Lastly, let’s update the channel members list by removing the user that left from the list. We’ll implement that inside the initializeChannel method. Add the code below immediately after where we are listening for when a new member is added to the channel:

// resources/js/ChatRoom.vue

this.channel.on("member.removed", event => {
  this.members = this.members.filter(member => {
    return member.user.id !== event.user.id;
  });
});

Here, we are listening for the member.removed event and filter the members to not include the user that left.

Joining a Channel

Stream Chat also provides an addMembers function (which we saw in the last tutorial) for adding users a channel. Add the following code inside ChatController.php:

// app/Http/Controllers/ChatController.php

public function joinChannel(Request $request)
{
    $username = explode('@', $request->email)[0];

    $this->channel->addMembers([$username]);

    return redirect('/home');
}

In this method, we create the username by extracting part of the information sent to us before calling the addMembers function on the current channel instance. To trigger this method, open api.php and add the code below:

// routes/api.php

Route::post('/join-channel', 'ChatController@joinChannel')->name('join-channel');

Next, we need a button on the user side to trigger this action. We’ll add it to the dashboard page. This is the page the user gets redirected to when they leave a channel. Create a dashboard.blade.php file in the resources/views directory and add the following code to it:

// resources/views/dashboard.blade.php

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">Dashboard</div>
                    <div class="card-body">
                        <p>You have left the channel</p>
                        <form action="{{ route('join-channel') }}" method="post">
                          <input type="hidden" name="email" value="{{ Auth::user()->email }}">
                          <button type="submit" class="mt-4 btn btn-warning">Join Channel</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

We created a small form and button to trigger the endpoint; we just created and passed along the necessary information to it. To round up this functionality, let’s add the required route and controller code to render this page. Open routes/web.php and add the following code to it:

// routes/web.php

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

Next, add the following code inside HomeController.php:

// app/Http/Controllers/HomeController.php

public function dashboard()
{
    return view('dashboard');
}

Displaying User Joined/Left Notification

When a user joins a chatroom, a cool feature to have is one where we can see the user name of the person that just entered. This can come in handy for introductions and other formalities so users won’t get lost in the chats.

Stream Chat triggers certain events, which we can listen to and perform any necessary action accordingly. We have seen a couple of these events in the last tutorial. To know when a user joins a channel, we listen for the member.added. event.

Inside the template section of resources/views/js/ChatRoom.vue`, add the following code immediately before the message form:

// resources/views/js/ChatRoom.vue

<span class="help-block" v-if="status" v-text="status" style="font-style: italic;"></span>
Building your own app? Get early access to our Livestream or Video Calling API and launch in days!

This will display the user joins notification only if the status property has been set.

Next, inside the script section of resources/views/js/ChatRoom.vue, let’s start by adding a new status property to the component’s data:

// resources/views/js/ChatRoom.vue

data() {
  return {
    ...
    status: ""
  };
},

Then inside the initializeChannel method, we’re already listening for the member.added event, all we need to do is update it as below:

// resources/views/js/ChatRoom.vue

this.channel.on("member.added", event => {
  this.members.push(event);

  this.status = `${event.user.name} joined the chat`;
});

The status property is initially set to an empty string. Once a new member joins the channel, we set status property accordingly.

Similarly, to display a notification when a user leaves a channel, we’re already listening for the member.removed event, all we need to do is update it as below:

// resources/views/js/ChatRoom.vue

this.channel.on("member.removed", event => {
  this.members = this.members.filter(member => {
    return member.user.id !== event.user.id;
  });
  
  this.status = `${event.user.name} just left the chat`;
});

Now, whenever a user leaves the channel, we’ll set status accordingly.

Adding a Typing Indicator

Another handy feature of chat applications is the ability to know when a user has started typing a response to a message — that way, we can anticipate better instead of having to sit and wonder.

Stream Chat provides two events regarding typing: typing.start and typing.stop, which we can use to check if a user is typing or not. Let’s add two additional data properties to the ChatRoom component:

// resources/js/components/ChatRoom.vue

data() {
  return {
    ...
    isTyping: false,
    typing: null
  };
},

isTyping will be used to indicate whether a user is typing or not, while typing will hold the response gotten as a result of triggering the events.

Next, let’s update the message field so we can keep track of when a user is typing or not:

// resources/js/components/ChatRoom.vue

<input
  type="text"
  v-model="newMessage"
  @keydown="startedTyping"
  @keyup="stoppedTyping"
  class="form-control"
  placeholder="Type your message..."
/>

We add two keyboard events: @keydown and @keyup, which are Vue.js equivalent of the JavaScript keyboard onkeydown and onkeyup events, respectively. On keydown, the startedTyping method will be triggered, and on keyup, the stoppedTyping method will be triggered.

Next, let’s create those methods:

// resources/js/components/ChatRoom.vue

...
async startedTyping() {
  await this.channel.keystroke();
},
stoppedTyping() {
  setTimeout(async () => {
    await this.channel.stopTyping();
  }, 2000);
}
...

When the user starts typing (keydown), we need to trigger a typing.start event using the keystrokes function on the channel instance. Similarly, when the user stops typing (keyup) for more than two seconds, we need to trigger a typing.stop event using the stopTyping function on the channel instance.

To wrap up, we need to listen to these events as they are triggered. We’ll do that in the initializeChannel method:

// resources/js/components/ChatRoom.vue

...
this.channel.on("typing.start", event => {
  this.isTyping = true;
  this.typing = event;
});

this.channel.on("typing.stop", event => {
  this.isTyping = false;
});
...

Lastly, we need a way to display the typing indicator. Add the following code immediately after the message form:

// resources/js/components/ChatRoom.vue

<span
  class="help-block mt-2"
  v-if="isTyping && typing.user.id !== username"
  style="font-style: italic;">{{ `${typing.user.name} is typing...` }}</span>

We conditionally show the text {username} is typing when isTyping is true and if the user typing is not the same one viewing it.

Testing the App

Now, let’s test what we’ve been building so far. First, let’s compile our JavaScript:

npm run dev

Next, let’s start our application:

php artisan serve

Navigate to http://127.0.0.1:8000.

Final Thoughts

In this tutorial, we’ve looked into adding various functionality we can add to our chatroom application using Stream Chat. We’ve used events and functions provided by Stream Chat to help enhance our application, and the knowledge gained can be used to create more complex chat applications. To learn more about Stream Chat, check out the docs.

The complete code for this tutorial is available 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 ->