Build online presence into your Laravel app
A basic understanding of Laravel is needed to follow this tutorial. The tutorial will assume you’re starting a new Laravel 5.4 app.
Realtime status update, particularly without polling our application for any updates, has always been a challenge. To that end, Laravel released its 5.4 version recently with a new broadcasting library: Echo.
Instant messaging is the minimum feature requirement in any social network. With Laravel Echo and its broadcasting features, it is possible to implement features with realtime status updates like online or offline in matter of a 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 user-status-update-laravel-pusher
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
We require some form of user authentication mechanism to demonstrate the functionality. Let us use the default authentication scaffolding provided by Laravel:
php artisan make:auth
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 applications’s JavaScript. We can do so at the bottom of our resources/assets/js/bootstrap.js
file:
import Echo from "laravel-echo"
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-app-key',
cluster: 'ap2',
encrypted: true
});
Our application
We will implement a feature where a user can see whether their friend is online or offline (similar to what WhatsApp does) and update the status of a user in realtime. We will concentrate on the code necessary for implementing the status update feature in realtime - we will not cover anything relating to implementing a chat functionality using Laravel. The code is available on a Github repository for cloning and understanding purposes.
Migrations
We need to add some extra fields to the default migration of the users
table:
- A field to store the status of user -
online
oroffline
- A field to store the token for authenticating API requests
Below is the migration for our users
table:
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->string('api_token');
$table->string('status')->default('online');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
Online Status
Whenever a user is active on the page, we subscribe to the chat
channel and assume that a user is online. In the joining
callback, we trigger an API request to update the user’s status to online:
Echo.join('chat')
.joining((user) => {
axios.put('/api/user/'+ user.id +'/online?api_token=' + user.api_token, {});
});
join
is used when we want to join a presence channel. Presence channels are automatically private channels. We do need to authenticate them.
joining
will be executed whenever a new user joins the chat
channel.
Next, we broadcast the UserOnline
event:
# api.php
Route::middleware('auth:api')->put('user/{user}/online', 'UserOnlineController');
# UserOnlineController.php
namespace App\Http\Controllers;
use App\User;
use App\Events\UserOnline;
use Illuminate\Http\Request;
class UserOnlineController extends Controller
{
public function __invoke(User $user)
{
$user->status = 'online';
$user->save();
broadcast(new UserOnline($user));
}
}
Whenever a user comes online, we need to fire an event which will be broadcast over Pusher to a specific presence channel. For broadcasting an event, it should implement the ShouldBroadcast
interface. Let us first create the UserOnline
event:
php artisan make:event UserOnline
broadcastOn method
The event should implement a broadcastOn
method. This method should return the presence channel to which the event should be broadcast. All public properties in the event will be automatically broadcast to every user listening on the chat
channel.
namespace App\Events;
use App\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class UserOnline implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function broadcastOn()
{
return new PresenceChannel('chat');
}
}
Next, 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
Listening for online status
In our JavaScript, we need to listen to the UserOnline
event to update the status in realtime:
Echo.join('chat')
.listen('UserOnline', (e) => {
this.friend = e.user;
})
Now, whenever a user joins the chat
channel, the status of user will be updated in the database and be broadcast over Pusher to other friends present on the same channel.
Offline Status
Similarly, whenever a user leaves the chat
channel, we will update the user’s status to offline:
Echo.join('chat')
.leaving((user) => {
axios.put('/api/user/'+ user.id +'/offline?api_token=' + user.api_token, {});
})
Next, we update the status in the database and broadcast the UserOffline
event:
# api.php
Route::middleware('auth:api')->put('user/{user}/offline', 'UserOfflineController');
# UserOfflineController.php
namespace App\Http\Controllers;
use App\Events\UserOffline;
use App\User;
use Illuminate\Http\Request;
class UserOfflineController extends Controller
{
public function __invoke(User $user)
{
$user->status = 'offline';
$user->save();
broadcast(new UserOffline($user));
}
}
Let us now create the UserOffline
event:
php artisan make:event UserOffline
Below is our UserOffline.php
event:
namespace App\Events;
use App\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class UserOffline implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function broadcastOn()
{
return new PresenceChannel('chat');
}
}
Listening for offline status
We need to listen to the UserOffline
event to update the status in realtime:
Echo.join('chat')
.listen('UserOffline', (e) => {
this.friend = e.user;
});
Bringing it all together
Below is our Example
component which handles the realtime update of online and offline status of a user we are chatting with:
props: ['user', 'user2'],
data() {
return {
friend: this.user2
}
},
mounted() {
this.listen();
},
methods: {
listen() {
Echo.join('chat')
.joining((user) => {
axios.put('/api/user/'+ user.id +'/online?api_token=' + user.api_token, {});
})
.leaving((user) => {
axios.put('/api/user/'+ user.id +'/offline?api_token=' + user.api_token, {});
})
.listen('UserOnline', (e) => {
this.friend = e.user;
})
.listen('UserOffline', (e) => {
this.friend = e.user;
});
},
}
Authorization
Every presence channel is a private channel. Laravel Echo will automatically call the specified authentication route. However, we still need to write the authentication logic which will actually authorize the user to listen to a particular channel.
Authorization logic is written in the routes/channels.php
. The authorization logic for our chat
channel is:
Broadcast::channel('chat', function ($user) {
return $user;
});
We are not going to return true
or false
. If the user is authenticated to listen on this presence channel, we will return an array of data that we want to be returned to that callback in the listen
method.
We need to write the actual authorization logic which will check whether the user is allowed to access the specified chat room. If we don’t write proper authorization logic, then anyone can listen to a chat
channel and hijack our conversations.
The friend’s status (online or offline) is shown at the top of the chat box in realtime as demonstrated below:
Vue.js component
That’s it! Now, the user’s status will be broadcast and we can listen using our presence channel in realtime without the need to constantly poll our application.
Below is the JavaScript part of our Example component written using Vue.js
Status - {{ friend.status }}
<input type="text" @keyup="whisper()" />
<script>
export default {
props: ['user', 'user2'],
data() {
return {
friend: this.user2
}
},
mounted() {
this.listen();
},
methods: {
listen() {
Echo.join('chat')
.joining((user) => {
axios.put('/api/user/'+ user.id +'/online?api_token=' + user.api_token, {});
})
.leaving((user) => {
axios.put('/api/user/'+ user.id +'/offline?api_token=' + user.api_token, {});
})
.listen('UserOnline', (e) => {
this.friend = e.user;
})
.listen('UserOffline', (e) => {
this.friend = e.user;
});
}
}
}
</script>
Conclusion
In this article, we have covered how to update the user’s status in realtime using Laravel and Pusher. We have covered the configuration options necessary to get started, and the example above should help you fill in the gaps and give an overview of some of the other configuration options available to you.
13 March 2017
by Viraj Khatavkar