📢 Broadcasting with Laravel Echo & Vue.js

Daniel Alvidrez
6 min readFeb 19, 2018

--

Tutorial / Component Breakdown

Quasar Framework Layout

📦 Packages

npm install laravel-echo
npm install pusher-js
composer require pusher/pusher-php-server

👀 Review Config:

You’ll need to insure your config is setup to inject your Pusher credentials.

'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'encrypted' => true,
],
],

👀 Review: App\Providers\BroadcastServiceProvider.php

Via the Docs:

Before broadcasting any events, you will first need to register the App\Providers\BroadcastServiceProvider. In fresh Laravel applications, you only need to uncomment this provider in the providers array of your config/app.php configuration file. This provider will allow you to register the broadcast authorization routes and callbacks.

👀 Review: routes/broadcasting.php

Broadcasting routes are basically simple guards that will check an entity before allowing a private connection. Users must be authorized before connecting to private channels. We only need the default authentication channel to access our notifications for this demo.

Channel routes are authorization callbacks used to check if an authenticated user can listen to a channel. Instead of returning data, they return the authorized status for the private channel. Essentially they are routes that respond with only the guard status.

Endpoint: ‘http://localhost/broadcasting/auth'

Broadcast::channel('App.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});

**The name of each route reflects the namespace of the entity that requires authorization.

📝 Edit: Broadcasting Service Provider

In order to use our API routes with broadcasting routes. We must first update our BroadcastServiceProvider to use “auth:api” instead of “web”.
See: How to use Laravel’s Built-in Token Auth for setup details…

class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
* @return void
*/
public function boot()
{
Broadcast::routes(["middleware" => ["auth:api"]]);
require base_path('routes/channels.php');
}
}

📝 Modify: Login Method

You can load the currentUser’s notifications when they login. This will allow you to seed existing notifications into your component without an extra HTTP request. (You’ll need to assign the property to your reactive data)

response.data.currentUser.notifications

return response(array(
'currentUser' => $user->loadMissing('notifications'),
));

💎 Part 2: Component Setup

Let’s setup our component scaffolding. We’ll need to import our JS libraries as well as setup the properties and template.

<script>
import Echo from 'laravel-echo'
import Pusher from 'pusher-js'
export default {
name: 'laravel-echo',
data() {
return {
echo: null
}
},
computed: {
//Add computed properties
currentUser: { ...},
isConnected: { ...},
notifications: { ...},
},
watch: {
//Add watchers...
},
methods: {
//Add methods...
}
}
</script>
<template>
<div id="laravel-echo">
<template v-if="isConnected">
<ul v-for="object in notifications">
{{ object }}
</ul>
<button @click="disconnect">Disconnect</button>
</template>
<template v-else-if="currentUser">
<button @click="connect">Connect</button>
</template>
</div>
</template>

📝 Add: Computed Properties

Next we’ll need our authenticated user as well as the state of our connection and reactive storage property. In this example I’m using Vuex State Management. (you can use local properties instead of computed if you’re not using Vuex)

currentUser: { 
cache: false,
get(){ return this.$store.getters.currentUser }
},
notifications: {
cache: false,
get(){ return this.$store.getters.notifications.reverse() }
},
isConnected: {
cache: false,
get(){
return (this.echo && this.echo.connector.pusher.connection.connection !== null)
}
},

📝 Add Watchers: (currentUser, isConnected)

We can watch our state and automatically connect when the component is loaded and the currentUser is present as well as automatically disconnect when the user is null.

watch: {
currentUser: {
handler(currentUser){
(currentUser !== null ? this.connect() : this.disconnect())
}
},
isConnected: {
handler(isConnected){
this.$emit('broadcasting:status', isConnected)
}
}
},

🚦 Add Method: connect()

In this example we’ll be using the native Laravel api_token auth instead of web & CSRF. See: How to use Laravel’s Built-in Token Auth for setup details…

/** Connect Echo **/
connect(){
if(!this.echo){
this.echo = new Echo({
broadcaster: 'pusher',
key: 'XXX',
cluster: 'us2',
encrypted: true,
authEndpoint: 'http://localhost/broadcasting/auth',
auth: {
headers: {
Authorization: null
}
},
//csrfToken: null,
//namespace: 'App',
})
this.echo.connector.pusher.connection.bind('connected', (event) => this.connect(event))
this.echo.connector.pusher.connection.bind('disconnected', () => this.disconnect()) }
this.echo.connector.pusher.config.auth.headers.Authorization = 'Bearer ' + this.currentUser.api_token
this.echo.connector.pusher.connect()
}

📝 Add Method: bindChannels()

The bindChannels() method will bind the channel callbacks that commit each message to our $store (or will assign to local properties). *Note that Echo provides a method specifically for handling notifications. ** Because we’re dealing with an Array type, you’ll want to push() a new item into the array.

/** Bind Channels **/
bindChannels(){
let vm = this
this
.echo.private('App.User' + '.' + this.currentUser.id)
.notification((object) => vm.$store.commit('addNotification', object))
},

or for local data use:

this.echo.private('App.User' + '.' + this.currentUser.id)
.notification((object) => vm.notifications.push(object))

🚦 Add Method: disconnect()

The disconnect() method will allow us to toggle our connection on/off.

/** Disconnect Echo **/
disconnect(){
if(!this.echo) return
this
.echo.disconnect()
},

🔧Component Usage:

If you’re using Vue Router like I am you’ll want to place this in your root component so it will continue to work as users navigate the app.

<router-view/>...<broadcasting ref="broadcasting"/>this.$refs.broadcasting.connect()
this.$refs.broadcasting.disconnect()

⏰ Part 3: Notifications

php artisan make:notification TestBroadcastNotification

In this example we’re using notifications.

<?php namespace App\Notifications;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\BroadcastMessage;
class TestBroadcastNotification extends Notification
{
use Queueable;
public $object;
/**
* Create a new notification instance.
*
@param $object object
*
@return void
*/
public function __construct($object)
{
$this->object = $object;
}

/**
* Get the notification's delivery channels.
*
@param mixed $notifiable
*
@return array
*/
public function via($notifiable)
{
return [
//'database', (migration required)
//'mail',
'broadcast',
];
}

/**
* Get the broadcastable representation of the notification.
*
@param mixed $notifiable
*
@return BroadcastMessage
*/
public function toBroadcast($notifiable)
{
$timestamp = Carbon::now()->addSecond()->toDateTimeString();
return new BroadcastMessage(array(
'notifiable_id' => $notifiable->id,
'notifiable_type' => get_class($notifiable),
'data' => $this->object,
'read_at' => null,
'created_at' => $timestamp,
'updated_at' => $timestamp,
));
}
/**
* Get the mail representation of the notification.
*
*
@param mixed $notifiable
*
@return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('Thank you for using our application!');
}

/**
* Get the array representation of the notification.
*
@param mixed $notifiable
*
@return array
*/
public function toArray($notifiable)
{
return (array) $this->object;
}
}

👀 Review: toBroadcast() Method

Because our notification has not been saved to the database yet, we’ll need to mock our notification object to matched what’s loaded from our database when we query the user’s notifications normally. We can use Carbon to create a timestamp 1 second in the future to match our actual notification when it’s finally saved. (if you have a better way of dealing with this, please share!)

$timestamp = Carbon::now()->addSecond()->toDateTimeString();
return new BroadcastMessage(array(
'notifiable_id' => $notifiable->id,
'notifiable_type' => get_class($notifiable),
'data' => $this->object,
'read_at' => null,
'created_at' => $timestamp,
'updated_at' => $timestamp,
));

📝 Add: Test Route

routes/api.php

Route::any('test', function(){
$user = \App\User::findOrFail(1);
$data = (object) array(
'test' => 123,
);
$user->notify(new \App\Notifications\TestBroadcastNotification($data));
return response()->json($data);
});

❓Get it Working?

If all went as planed you should see the status in your console log.

Firefox Dark Theme

🙌 Final Wrap Up

Using real-time data is a great way to enliven the user experience of your application. I highly suggest you try it.

--

--

Daniel Alvidrez

Full Stack Developer — Community Moderator @ Laravel PHP Framework Facebook Group