Update: as of 13 October 2021 a newer version of this tutorial is available on how to create a chat app with Vue.js and Laravel 8. Find the new guide here.
Laravel makes it easy to build modern applications with realtime interactions by providing an event broadcasting system which allows developers to share the same event names between the server-side code and the client-side JavaScript application.
Pusher Channels, on the other hand, is an easy and reliable platform for building scalable realtime applications. Laravel provides support for Pusher Channels out of the box, which makes building realtime applications with Laravel and Pusher Channels seamless. In fact, Pusher Channels has emerged as one of the Laravel community’s preferred tools to make apps realtime, thanks to the support of Taylor Otwell, Jeffrey Way, Matt Stauffer, and many more.
In this post, I will be showing you how to build a laravel chat application with Pusher Channels. I will be using Vue.js as my JavaScript framework, although you can use the JavaScript framework of your choice or even jQuery and vanilla JavaScript.
Before we start, let's take a quick look at what we'll be building.
The code of the completed demo is available on GitHub.
We'll start by creating a new Laravel project. While there are different ways of creating a new Laravel project, I prefer using the Laravel installer. Open your terminal and run the code below:
laravel new laravel-chat
This will create a laravel-chat
project within the directory where you ran the command above.
Before we start using Laravel event broadcasting, we first need to register the App\Providers\BroadcastServiceProvider
. Open config/app.php
and uncomment the following line in the providers
array.
// App\Providers\BroadcastServiceProvider
We need to tell Laravel that we are using the Pusher driver in the .env
file:
1// .env 2 3BROADCAST_DRIVER=pusher
Though Laravel supports Pusher out of the box, we still need to install the Pusher PHP SDK. We can do this using composer:
composer require pusher/pusher-php-server
Once the installation is done, we need to configure our Pusher app credentials in config/broadcasting.php
. To get our Pusher app credential, we need to have a Pusher account.
To get started with Pusher Channels, sign up for a Pusher account. Then go to the dashboard and create a new Channels app.
Now, let's fill in our Channels app credentials. If you open the config/broadcasting.php
, you'll notice that Laravel is pulling some of Pusher credential from the .env
file:
1// Don't add your credentials here! 2// config/broadcasting.php 3 4'pusher' => [ 5 'driver' => 'pusher', 6 'key' => env('PUSHER_APP_KEY'), 7 'secret' => env('PUSHER_APP_SECRET'), 8 'app_id' => env('PUSHER_APP_ID'), 9 'options' => [], 10],
We need to modify the source a little bit here to get this to work. Modify the source so that it looks like this:
1'pusher' => [ 2 'driver' => 'pusher', 3 'key' => env('PUSHER_APP_KEY'), 4 'secret' => env('PUSHER_APP_SECRET'), 5 'app_id' => env('PUSHER_APP_ID'), 6 'options' => [ 7 'cluster' => env('PUSHER_CLUSTER'), 8 'encrypted' => true, 9 ], 10 ],
Then let's update the .env
file to contain our Pusher app credentials (noting the added cluster credential, this won't be in your .env
file as Laravel has not added this functionality yet:
1// .env 2 3PUSHER_APP_ID=xxxxxx 4PUSHER_APP_KEY=xxxxxxxxxxxxxxxxxxxx 5PUSHER_APP_SECRET=xxxxxxxxxxxxxxxxxxxx 6PUSHER_CLUSTER=xx
Remember to replace the x
s with your Pusher app credentials. You can find your app credentials under the Keys section on the Overview tab.
Now that we've set up the back-end of our project, let's move on to setting up the front-end. Laravel provides some front-end frameworks and libraries, including - Bootstrap
, Vuejs
and Axios
which we'll be using in this tutorial.
We'll also be making use of Laravel Mix, which is a wrapper around Webpack that will help us compile our CSS and JavaScript.
But first, we need to install these dependencies through NPM
:
npm install
To subscribe and listen to events, Laravel provides Laravel Echo, which is a JavaScript library that makes it painless to subscribe to channels and listen for events broadcast by Laravel. We'll need to install it along with the Pusher JavaScript library:
npm install --save laravel-echo pusher-js
Once installed, we need to tell Laravel Echo to use Pusher. At the bottom of the resources/assets/js/bootstrap.js
file, Laravel have stubbed Echo integration though it is commented out. Simply uncomment the Laravel Echo section and update the details with:
1// resources/assets/js/bootstrap.js 2 3import Echo from "laravel-echo" 4 5window.Echo = new Echo({ 6 broadcaster: 'pusher', 7 key: 'xxxxxxxxxxxxxxxxxxxx', 8 cluster: 'eu', 9 encrypted: true 10});
Remember to replace the x
s with your Pusher app key. Also use the same cluster
that you specified earlier in config/broadcasting.php
.
Now that we are done with setting up Laravel and Pusher and other dependencies, it time to start building our chat application.
Our chat app will require users to be logged in before they can begin to chat. So, we need an authentication system, which with Laravel is as simple as running an artisan
command in the terminal:
php artisan make:auth
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:
1// .env 2 3DB_CONNECTION=mysql 4DB_HOST=127.0.0.1 5DB_PORT=3306 6DB_DATABASE=laravel-chat 7DB_USERNAME=root 8DB_PASSWORD=root
Update with your own database details. Now, we can run our migration:
php artisan migrate
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:
1// app/Providers/AppServiceProvider.php 2 3// remember to use 4Illuminate\Support\Facades\Schema; 5 6/** 7 * Bootstrap any application services. 8 * 9 * @return void 10 */ 11public function boot() 12{ 13 Schema::defaultStringLength(191); 14}
Create a Message
model along with the migration file by running the command:
php artisan make:model Message -m
Open the Message
model and add the code below to it:
1// app/Message.php 2 3/** 4 * Fields that are mass assignable 5 * 6 * @var array 7 */ 8protected $fillable = ['message'];
Within the databases/migrations
directory, open the messages
table migration that was created when we ran the command above and update the up
method with:
1Schema::create('messages', function (Blueprint $table) { 2 $table->increments('id'); 3 $table->integer('user_id')->unsigned(); 4 $table->text('message'); 5 $table->timestamps(); 6});
The message
will have five columns: an auto increment id
, user_id
, message
, created_at
and updated_at
. The user_id
column will hold the ID of the user that sent a message and the message
column will hold the actual message that was sent. Run the migration:
php artisan migrate
We need to setup the relationship between a user and a message. A user can send many messages while a particular message was sent by a user. So, the relationship between the user and message is a one to many relationship. To define this relationship, add the code below to User
model:
1// app/User.php 2 3/** 4 * A user can have many messages 5 * 6 * @return \Illuminate\Database\Eloquent\Relations\HasMany 7 */ 8public function messages() 9{ 10 return $this->hasMany(Message::class); 11}
Next, we need to define the inverse relationship by adding the code below to Message
model:
1// app/Message.php 2 3/** 4 * A message belong to a user 5 * 6 * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 7 */ 8public function user() 9{ 10 return $this->belongsTo(User::class); 11}
Let's create the routes our chat app will need. Open routes/web.php
and replace the routes with the code below to define three simple routes:
1// routes/web.php 2 3Auth::routes(); 4 5Route::get('/', 'ChatsController@index'); 6Route::get('messages', 'ChatsController@fetchMessages'); 7Route::post('messages', 'ChatsController@sendMessage');
The homepage will display chat messages and an input field to type new messages. A GET
messages
route will fetch all chat messages and a POST
messages
route will be used for sending new messages.
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 = '/';
Now let's create the controller which will handle the logic of our chat app. Create a ChatsController
with the command below:
php artisan make:controller ChatsController
Open the new create app/Http/Controllers/ChatsController.php
file and add the following code to it:
1// app/Http/Controllers/ChatsController.php 2 3use App\Message; 4use Illuminate\Http\Request; 5use Illuminate\Support\Facades\Auth; 6 7public function __construct() 8{ 9 $this->middleware('auth'); 10} 11 12/** 13 * Show chats 14 * 15 * @return \Illuminate\Http\Response 16 */ 17public function index() 18{ 19 return view('chat'); 20} 21 22/** 23 * Fetch all messages 24 * 25 * @return Message 26 */ 27public function fetchMessages() 28{ 29 return Message::with('user')->get(); 30} 31 32/** 33 * Persist message to database 34 * 35 * @param Request $request 36 * @return Response 37 */ 38public function sendMessage(Request $request) 39{ 40 $user = Auth::user(); 41 42 $message = $user->messages()->create([ 43 'message' => $request->input('message') 44 ]); 45 46 return ['status' => 'Message Sent!']; 47}
Using the auth
middleware in ChatsController
's __contruct()
indicates that all the methods with the controller will only be accessible to authorized users. Then the index()
will simply return a view file which we will create shortly. The fetchMessages()
return a JSON
of all messages along the their users. Lastly, the sendMessage()
will persist the message into the database and return a status message.
For the chat app view, we'll be making use of Bootsnipp chat snippet with some few modifications.
Create a new resources/views/chat.blade.php
file and paste into it:
1<!-- resources/views/chat.blade.php --> 2 3@extends('layouts.app') 4 5@section('content') 6 7<div class="container"> 8 <div class="row"> 9 <div class="col-md-8 col-md-offset-2"> 10 <div class="panel panel-default"> 11 <div class="panel-heading">Chats</div> 12 13 <div class="panel-body"> 14 <chat-messages :messages="messages"></chat-messages> 15 </div> 16 <div class="panel-footer"> 17 <chat-form 18 v-on:messagesent="addMessage" 19 :user="{{ Auth::user() }}" 20 ></chat-form> 21 </div> 22 </div> 23 </div> 24 </div> 25</div> 26@endsection
Notice we have some custom tags with the chat
view, these are Vue
components which we'll create soon. The chat-messages
component will display our chat messages and the chat-form
will provide an input field and a button to send the messages.
Before we go to create our Vue
component, let's add the styles for the chat
view. Open resources/views/layouts/app.blade.php
(which was created when we ran make:auth
) and add the code below just after the styles link:
1<!-- resources/views/layouts/app.blade.php --> 2 3<style> 4 .chat { 5 list-style: none; 6 margin: 0; 7 padding: 0; 8 } 9 10 .chat li { 11 margin-bottom: 10px; 12 padding-bottom: 5px; 13 border-bottom: 1px dotted #B3A9A9; 14 } 15 16 .chat li .chat-body p { 17 margin: 0; 18 color: #777777; 19 } 20 21 .panel-body { 22 overflow-y: scroll; 23 height: 350px; 24 } 25 26 ::-webkit-scrollbar-track { 27 -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 28 background-color: #F5F5F5; 29 } 30 31 ::-webkit-scrollbar { 32 width: 12px; 33 background-color: #F5F5F5; 34 } 35 36 ::-webkit-scrollbar-thumb { 37 -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); 38 background-color: #555; 39 } 40</style>
Looking at the resources/assets/js/bootstrap.js
, you will notice that Laravel has set up some of the front-end dependencies (jQuery, Bootstrap, Lodash, Vue, Axios, Echo) that are included out of the box. We can start using Vue
without any further setup.
Create a new ChatMessages.vue
file within resources/assets/js/components
and paste the code below into it:
1// resources/assets/js/components/ChatMessages.vue 2 3<template> 4 <ul class="chat"> 5 <li class="left clearfix" v-for="message in messages"> 6 <div class="chat-body clearfix"> 7 <div class="header"> 8 <strong class="primary-font"> 9 {{ message.user.name }} 10 </strong> 11 </div> 12 <p> 13 {{ message.message }} 14 </p> 15 </div> 16 </li> 17 </ul> 18</template> 19 20<script> 21 export default { 22 props: ['messages'] 23 }; 24</script>
This component accepts an array of messages as props
, loops through them and displays the name of the user who sent the message and the message body.
Next, create a new ChatForm.vue
file within resources/assets/js/components
and paste the code below into it:
1// resources/assets/js/components/ChatForm.vue 2 3<template> 4 <div class="input-group"> 5 <input id="btn-input" type="text" name="message" class="form-control input-sm" placeholder="Type your message here..." v-model="newMessage" @keyup.enter="sendMessage"> 6 7 <span class="input-group-btn"> 8 <button class="btn btn-primary btn-sm" id="btn-chat" @click="sendMessage"> 9 Send 10 </button> 11 </span> 12 </div> 13</template> 14 15<script> 16 export default { 17 props: ['user'], 18 19 data() { 20 return { 21 newMessage: '' 22 } 23 }, 24 25 methods: { 26 sendMessage() { 27 this.$emit('messagesent', { 28 user: this.user, 29 message: this.newMessage 30 }); 31 32 this.newMessage = '' 33 } 34 } 35 } 36</script>
The ChatForm
component displays an input field and a send button. It accepts the authenticated user as props
. It also contains newMessage
data which is bound to the input field. When the send button is clicked or the enter key is pressed on the input field, a sendMessage()
is called. The sendMessage()
simply triggers a messagesent
event which passes along the message that was sent by the user to the root Vue
instance (which will handle the actual sending of the message) and finally clear the input filed.
Next, we need to register our component in the root Vue
instance. Open the resources/assets/js/app.js
and update with code below:
1// resources/assets/js/app.js 2 3require('./bootstrap'); 4 5Vue.component('chat-messages', require('./components/ChatMessages.vue')); 6Vue.component('chat-form', require('./components/ChatForm.vue')); 7 8const app = new Vue({ 9 el: '#app', 10 11 data: { 12 messages: [] 13 }, 14 15 created() { 16 this.fetchMessages(); 17 }, 18 19 methods: { 20 fetchMessages() { 21 axios.get('/messages').then(response => { 22 this.messages = response.data; 23 }); 24 }, 25 26 addMessage(message) { 27 this.messages.push(message); 28 29 axios.post('/messages', message).then(response => { 30 console.log(response.data); 31 }); 32 } 33 } 34});
Once the Vue
instance is created, using Axios
, we make a GET
request to the messages
route and fetch all the messages then pass it to the messages array that will be displayed on the chat
view. The addMessage()
receives the message that was emitted from the ChatForm
component, pushes it to the messages array and makes a POST
request to the messages
route with the message.
To add the realtime interactions to our chat app, we need to broadcast some kind of events based on some activities. In our case, we'll fire a MessageSent
when a user sends a message. First, we need to create an event, we'll call it MessageSent
:
php artisan make:event MessageSent
This will create a new MessageSent
event class within the app/Events
directory. This class must implement the ShouldBroadcast
interface. The class should look like:
1// app/Events/MessageSent.php 2 3use App\User; 4use App\Message; 5use Illuminate\Broadcasting\Channel; 6use Illuminate\Queue\SerializesModels; 7use Illuminate\Broadcasting\PrivateChannel; 8use Illuminate\Broadcasting\PresenceChannel; 9use Illuminate\Foundation\Events\Dispatchable; 10use Illuminate\Broadcasting\InteractsWithSockets; 11use Illuminate\Contracts\Broadcasting\ShouldBroadcast; 12 13class MessageSent implements ShouldBroadcast 14{ 15 use Dispatchable, InteractsWithSockets, SerializesModels; 16 17 /** 18 * User that sent the message 19 * 20 * @var User 21 */ 22 public $user; 23 24 /** 25 * Message details 26 * 27 * @var Message 28 */ 29 public $message; 30 31 /** 32 * Create a new event instance. 33 * 34 * @return void 35 */ 36 public function __construct(User $user, Message $message) 37 { 38 $this->user = $user; 39 $this->message = $message; 40 } 41 42 /** 43 * Get the channels the event should broadcast on. 44 * 45 * @return Channel|array 46 */ 47 public function broadcastOn() 48 { 49 return new PrivateChannel('chat'); 50 } 51}
We defined two public properties that will be the data that will be passed along to the channel we are broadcasting to.
NOTE: These properties must be public for it to be passed along to the channel.
Since our chat app is an authenticated-only app, we create a private channel called Chat
, which only authenticated users will be able to connect to. Using the PrivateChannel
class, Laravel is smart enough to know that we are creating a private channel, so don't prefix the channel name with private-
(as specified by Pusher), Laravel will add the private-
prefix under the hood.
Next, we need to update the sendMessage()
of ChatsController
to broadcast the MessageSent
event:
1// app/Http/Controllers/ChatsController.php 2 3//remember to use 4use App\Events\MessageSent; 5 6/** 7 * Persist message to database 8 * 9 * @param Request $request 10 * @return Response 11 */ 12public function sendMessage(Request $request) 13{ 14 $user = Auth::user(); 15 16 $message = $user->messages()->create([ 17 'message' => $request->input('message') 18 ]); 19 20 broadcast(new MessageSent($user, $message))->toOthers(); 21 22 return ['status' => 'Message Sent!']; 23}
Since we created a private channel, only authenticated users will be able to listen on the chat
channel. So, 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:
1// routes/channels.php 2 3Broadcast::channel('chat', function ($user) { 4 return Auth::check(); 5});
We pass to the channel(),
the name of our channel and a callback function that will either return true
or false
depending on whether the current user is authenticated.
Now when a message is sent, the MessageSent
event will be broadcast to Pusher. We are using the toOthers()
which allows us to exclude the current user from the broadcast's recipients.
Once the MessageSent
event is broadcast, we need to listen for this event so we can update the chat messages with the newly sent message. We can do so by adding the code snippet below to created()
of resources/assets/js/app.js
just after this.fetchMessages()
:
1// resources/assets/js/app.js 2 3Echo.private('chat') 4 .listen('MessageSent', (e) => { 5 this.messages.push({ 6 message: e.message.message, 7 user: e.user 8 }); 9 });
We subscribe to the chat
channel using Echo's private()
since the channel is a private channel. Once subscribed, we listen for the MessageSent
and based on this, update the chat messages array with the newly sent message.
Before testing out our chat app, we need to compile the JavaScript files using Laravel Mix using:
npm run dev
Now we can start our chat app by running:
php artisan serve
Our chat app is done as we can now send and receive messages in realtime.
You can see how straightforward it is to build a realtime app with Laravel and Pusher. With Pusher, you are not limited to chat apps, you can build any application that requires realtime interactivity. So, go create a free Pusher account and start building great applications!