Build a group chat app using Laravel
A basic understanding of Laravel and Vue.js are needed to follow this tutorial.
Socializing with other users has been a trend for past few years and it is definitely not going away. Having a public chat feature where users can communicate within a group or among their friends is a great add-on to many applications.
Today, we will create a realtime Group Chat Application using Laravel and Pusher. With the release of Echo, Laravel has provided an out of the box solution for implementing a realtime chat application using event broadcasting. It is quite simple to get started in a matter of few minutes.
Setup an app on Pusher
We need to sign up on Pusher and create a new app.
Install Laravel, Pusher SDK and Echo
First, we will grab a fresh copy of Laravel:
laravel new group-chat-app-pusher-laravel
This will install the latest version of the Laravel framework and download the necessary dependencies. Next, we will install the Pusher PHP SDK using Composer:
composer require pusher/pusher-php-server
Next, we will install the JavaScript dependencies:
npm install
Now, we need to install two JavaScript libraries necessary for realtime event broadcasting: Laravel Echo and Pusher JS:
npm install --save laravel-echo pusher-js
Configuration
First, we need to set the APP_ID
, APP_KEY
, APP_SECRET
and APP_CLUSTER
in the environment file. We can get these details in our Pusher app dashboard:
# .env
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=your-pusher-app-id
PUSHER_APP_KEY=your-pusher-app-key
PUSHER_APP_SECRET=your-pusher-app-secret
PUSHER_APP_CLUSTER=your-pusher-app-cluster
Next, we need to create a fresh Echo instance in our application’s JavaScript. We can do this at the bottom of our resources/assets/js/bootstrap.js
file:
import Echo from "laravel-echo"
window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-app-key',
cluster: 'your-pusher-app-cluster',
encrypted: true
});
Our application
We will implement a feature where multiple users can chat with each other in a single chat box. The code is available on a Github repository for cloning and understanding purposes.
Migrations
Next, we need a conversations
, groups
and group_users
table to record all the messages sent by a user in a particular group. Let us create the models and migrations:
php artisan make:model Conversation -m
php artisan make:model Group -m
php artisan make:migration create_group_user_table --create=group_user
The groups
table will require the following field:
- A field to store the name of the group
Below is our migration file for the groups
table:
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateGroupsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('groups', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('groups');
}
}
Next, the migration file for group_user
table will be as follows:
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateGroupUserTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('group_user', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('group_id');
$table->unsignedInteger('user_id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('group_user');
}
}
Lastly, the conversations
table will require the following fields:
- A field to store the body of each message
- A field to store the ID of the user who sent the message
- A field to store the ID of the group where the message is being sent
Below is our migration file for the conversations
table:
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateConversationsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('conversations', function (Blueprint $table) {
$table->increments('id');
$table->text('message')->nullable();
$table->unsignedInteger('user_id');
$table->unsignedInteger('group_id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('conversations');
}
}
Creating a new group
Before a user can start chatting with their friends, they need to create a group and add users to it.
First, we will create a GroupController
:
php artisan make:controller GroupController
Next, we will record the group entry into the groups
table and attach the users belonging to that group in the group_user
table:
# routes/web.php
Route::resource('groups', 'GroupController');
# GroupController.php
public function store()
{
$group = Group::create(['name' => request('name')]);
$users = collect(request('users'));
$users->push(auth()->user()->id);
$group->users()->attach($users);
return $group;
}
Broadcasting the new group
Whenever a new group is created, the users in the group should get the chat popup in realtime.
We need to fire an event which will be broadcast over Pusher to the users belonging to the group. For broadcasting an event, it should implement the ShouldBroadcast
interface. Let us first create the GroupCreated
event:
php artisan make:event GroupCreated
broadcastOn method
The event should implement a broadcastOn
method. This method should return the channels to which the event will be broadcast.
We created a closed group specific to some particular users. Thus, the event should be broadcast only to those users who are present in the group. We can achieve this as follows:
public function broadcastOn()
{
$channels = [];
foreach ($this->group->users as $user) {
array_push($channels, new PrivateChannel('users.' . $user->id));
}
return $channels;
}
Now, we need to start our queue to actually listen for jobs and broadcast any events that are recorded. We can use the database queue listener on our local environment:
php artisan queue:listen
Next, we need to broadcast this event to other users in the same group. Let us use the broadcast
helper provided by Laravel to fire the event whenever a new group is created:
public function store()
{
$group = Group::create(['name' => request('name')]);
$users = collect(request('users'));
$users->push(auth()->user()->id);
$group->users()->attach($users);
broadcast(new GroupCreated($group))->toOthers();
return $group;
}
Authorization
As we are listening on a private channel, we need to authenticate that the currently logged in user is able to listen on this private channel. Laravel Echo will automatically call the necessary authorization routes if we are listening to a private channel. But, we need to write the authentication logic which will actually authorize the user.
Authorization logic is written in the routes/channels.php
Broadcast::channel('users.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
Listening for new group chats
Installation and configuration of Laravel Echo is a must before we can start listening to new events. We have covered the process in detail in the above section of this article. Please go through it if you might have skipped it.
We can listen to new messages on a private channel using Echo.private(channel)
:
# Groups.vue
listenForNewGroups() {
Echo.private('users.' + this.user.id)
.listen('GroupCreated', (e) => {
this.groups.push(e.group);
});
}
Now, whenever a new group is created, it is broadcast over Pusher only to the specific group members. Next, we listen to the private channel and push the new groups to our group’s array in our Vue component.
Recording new messages
Once a user has access to the group, they can communicate with the other users in the same group by exchanging messages in a single chat window.
First, we will create a ConversationController
:
php artisan make:controller ConversationController
Next, we will record each message entry into the database.
# routes/web.php
Route::resource('conversations', 'ConversationController');
# ConversationController.php
public function store()
{
$conversation = Conversation::create([
'message' => request('message'),
'group_id' => request('group_id'),
'user_id' => auth()->user()->id,
]);
return $conversation->load('user');
}
Broadcasting messages
Whenever a new message is recorded, we need to fire an event which will be broadcast over Pusher. For broadcasting an event, it should implement the ShouldBroadcast
interface. Let us first create the NewMessage
event:
php artisan make:event NewMessage
broadcastOn method
The event should implement a broadcastOn
method. This method should return the channels to which the event will be broadcast.
Our chat is not public and hence, we will broadcast the messages only to the group’s private channel:
public function broadcastOn()
{
return new PrivateChannel('groups.' . $this->conversation->group->id);
}
Now, we need to start our queue to actually listen for jobs and broadcast any events that are recorded. We can use the database queue listener on our local environment:
php artisan queue:listen
Next, we need to broadcast this event to other users on the same group channel. Let us use the broadcast
helper provided by Laravel to fire the event whenever a new message is recorded:
public function store()
{
$conversation = Conversation::create([
'message' => request('message'),
'group_id' => request('group_id'),
'user_id' => auth()->user()->id,
]);
$conversation->load('user');
broadcast(new NewMessage($conversation))->toOthers();
return $conversation->load('user');
}
Authorization
Authorization logic for chat channels is written in the routes/channels.php
Broadcast::channel('groups.{group}', function ($user, Group $group) {
return $group->hasUser($user->id);
});
Listening to new messages
Being a private group chat application, we can listen to new messages on a private channel using Echo.private(channel)
:
listenForNewMessage() {
Echo.private('groups.' + this.group.id)
.listen('NewMessage', (e) => {
this.conversations.push(e);
});
}
Now, whenever a new message is recorded, it is broadcast over Pusher. Next, we listen to that channel and push the new conversations to our conversations array in our Vue component.
Below is the JavaScript part of our component written using Vue.js
<script>
export default {
props: ['group'],
data() {
return {
conversations: [],
message: '',
group_id: this.group.id
}
},
mounted() {
this.listenForNewMessage();
},
methods: {
store() {
axios.post('/conversations', {message: this.message, group_id: this.group.id})
.then((response) => {
this.message = '';
this.conversations.push(response.data);
});
},
listenForNewMessage() {
Echo.private('groups.' + this.group.id)
.listen('NewMessage', (e) => {
// console.log(e);
this.conversations.push(e);
});
}
}
}
</script>
Below is the demonstration of our whole functionality of creating new groups and chatting with the members.
Conclusion
In this article, we have demonstrated how to create a group chat application. We have covered the configuration options necessary to get started, and the examples above should help you fill in the gaps and give an overview of some of the other configuration options available to you.
The code is hosted on a public Github repository. You can download it for educational purposes. How do you use Laravel and Pusher for chat applications? Can you think of any advanced use cases for this library? What are they? Let us know in the comments!
7 June 2017
by Viraj Khatavkar