mrkaluzny homepage
Tips & Tricks

Using Laravel Echo with Socket.io and Vue SPA

Mar 20, 2020

Working on the latest project I found it necessary to implement Laravel Echo for live-updating features across the application such as timers, notifications or other small updates.

It took me a couple of hours to get Laravel Echo running on my development environment and staging one. So I decided to share my process.

Please notice! I'm using standalone Vue PWA application hosted on Netlify, while my backend application is ‌stored on Digital Ocean droplet and managed by Laravel Forge.

Let's get started with Laravel Echo

There's a couple drivers you can use, easiest to set up is Pusher, but since it requires an additional subscription, I've decided to go with Socket.io and Redis setup.

Let's first set up our backend. Configuration is done through .env file in the root of your application.

BROADCAST_DRIVER=redis
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

REDIS_PASSWORD=null
REDIS_PORT=6379

We need to change BROADCAST_DRIVER to Redis and update the ports Redis will work on. A specific configuration for Redis can be found in the config/database.php file.

'redis' => [

        'client' => env('REDIS_CLIENT', 'phpredis'),

        'options' => [
            'cluster' => env('REDIS_CLUSTER', 'redis'),
            'prefix' => '',
        ],

        'default' => [
            'url' => env('REDIS_URL'),
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', '6379'),
            'database' => env('REDIS_DB', '0'),
        ],

        'cache' => [
            'url' => env('REDIS_URL'),
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', '6379'),
            'database' => env('REDIS_CACHE_DB', '1'),
        ],

    ],
];

As you can see I'm using phpredis as default client for Redis. This means we have to install Redis on the Homestead box.

## Let's ssh into the box
$ homestead ssh

## Install Redis
$ pecl install redis

## Install phpredis
$ sudo git clone https://github.com/phpredis/phpredis.git
$ sudo mv phpredis/ /etc/

Configuration for Laravel Echo Server

Now we need a way to run our server for socket.io communication. We'll use laravel-echo-server package.

## On our homestead box
$ npm install -g laravel-echo-server

## In your project root
$ laravel-echo-server init

Init method will start the setup process to set up your echo server.

? Do you want to run this server in development mode? \
    Yes

? Which port would you like to serve from?\
    6001

? Which database would you like to use to store presence channel members?
    redis

? Enter the host of your Laravel authentication server.
    your-domain.test

? Will you be serving on http or https? \
    http

? Do you want to generate a client ID/Key for HTTP API?
    No

? Do you want to setup cross domain access to the API?
    Yes

? Specify the URI that may access the API: *

? Enter the HTTP methods that are allowed for CORS:
		GET, POST

? Enter the HTTP headers that are allowed for CORS:
		Origin, Content-Type, X-Auth-Token, X-Requested-With, Accept, Authorization, X-CSRF-TOKEN, X-Socket-Id

? What do you want this config to be saved as?
		laravel-echo-server.json

This process will result in generating your server configuration. For authorization, I'm also using API namespace for Laravel so I made a change in the generated .json file.

{
    ...
    "authEndpoint": "/api/broadcasting/auth",
    ...
}

Now authentification will work via a call to 'API' namespace route supporting all of my authorization guards in Laravel.

I've added laravel-echo-server.json to .gitignore to set it up separately on staging and production server, but you can also use .env file to overwrite settings.

If a .env file is found in the same directory as the laravel-echo-server.json file, the following options can be overridden:

# Note: This option will fall back to the LARAVEL_ECHO_SERVER_HOST option as the default if that is set in the .env file.
authHost: LARAVEL_ECHO_SERVER_AUTH_HOST
host: LARAVEL_ECHO_SERVER_HOST
port: LARAVEL_ECHO_SERVER_PORT
devMode: LARAVEL_ECHO_SERVER_DEBUG
databaseConfig.redis.host: LARAVEL_ECHO_SERVER_REDIS_HOST
databaseConfig.redis.port: LARAVEL_ECHO_SERVER_REDIS_PORT
databaseConfig.redis.password: LARAVEL_ECHO_SERVER_REDIS_PASSWORD
protocol: LARAVEL_ECHO_SERVER_PROTO
sslKeyPath: LARAVEL_ECHO_SERVER_SSL_KEY
sslCertPath: LARAVEL_ECHO_SERVER_SSL_CERT
sslPassphrase: LARAVEL_ECHO_SERVER_SSL_PASS
sslCertChainPath: LARAVEL_ECHO_SERVER_SSL_CHAIN

Enabling Laravel Echo server

The final step is to make Laravel Echo Server alive and keep it that way. This step will be different for forge managed server and homestead.

Running Laravel Echo Server on Homestead

After creating your server configuration you need to run a couple of commands to make sure the server is running.

# On homestead machine
$ cd /etc/supervisor/conf.d
$ touch laravel-echo.conf

# Edit this new file using nano or vim and add this
[program:laravel-echo]
directory=/home/vagrant/[path_to_your_project]
process_name=%(program_name)s_%(process_num)02d
command=laravel-echo-server start
autostart=true
autorestart=true
user=vagrant
numprocs=1
redirect_stderr=true
stdout_logfile=/home/vagrant/[path_to_your_project]/storage/logs/echo.log
stderr_logfile=/var/log/supervisor/test.err.log

# Lets reload the configuration and start the daemon
$ sudo supervisorctl reread
$ sudo supervisorctl restart all
laravel-echo:laravel-echo_00: started

After that our Laravel Echo Server will be kept alive and available to our front end application.

Running Laravel Echo Server on Forge

Running Laravel Echo Server on Forge is much easier than on Homestead. All we have to do is configure a daemon on our server.

To add a new one fill in the details

# Command
/usr/bin/laravel-echo-server start

# User
Forge

# Directory
/home/forge/[project-domain]

That's it! After starting you can check your process status in Laravel Forge.

Setting up Laravel Echo with Vue SPA application

To set up Laravel Echo with our front-end application we'll need to install a front end Laravel Echo package and Socket.io package

# In your front-end app root directory
$ yarn add laravel-echo socket.io

I've set up Laravel Echo in a file called echo.js, to import it into components where I need it. Here's the configuration file.

import Vue from 'vue';
import Echo from 'laravel-echo';
import Cookies from 'js-cookie';

window.io = require('socket.io-client');

export var echo_instance = new Echo({
  broadcaster: 'socket.io',
  host: process.env.VUE_APP_BACKEND_APP + ':6001',
  auth: {
    headers: {
      /** I'm using access tokens to access  **/
      Authorization: 'Bearer ' + Cookies.get('access_token'),
    },
  },
});

Vue.prototype.$echo = echo_instance;

export default Vue;

As a broadcaster, we're using socket.io. The rest of the configuration is really basic except for the authorization header. Bearer token is used to authorize users and grant access to channels from Laravel.

Listen to broadcasts

There's a couple of ways to listening for changes with Laravel Echo. I've implemented some of them in the application I'm currently working on.

In the root component of the application, I'm initiating a connection to the notifications channel for each user.

this.$echo.private(`user.${this.user.id}`).notification((notification) => {
  this.handleNotification(notification);
});

The snippet is wrapped into a method in the component and called after the user is logged in.

The other case is connected with real-time changes on private channels. I used this in the application to monitor changes to running timers in the application.

this.$echo
  .private(`timers.${this.game.timer.id}`)
  .listen('.timer.updated', (e) => {
    this.timer = e.timer;
    this.initializeTimer();
  });

This function is called when the component is mounted to connect user to the correct channel and always listen to changes made by other users, to show consistent data.

UPDATE [24-04-2020]: Couldn't connect to Redis ([ioredis] Unhandled error event: Error: connect ECONNREFUSED 192.168.10.10:6379)

When migrating my project to newer machine I noticed a problem with Echo server not being able to connect to Redis. This was caused by a missing configuration in Redis configuration.

The following error popped up when trying to run the Laravel echo server

$ [ioredis] Unhandled error event: Error: connect ECONNREFUSED 192.168.10.10:6379

To edit adjust this error, ssh into homestead machine:

$ sudo nano /etc/redis/redis.conf // edit this file

In /etc/redis/redis.conf add an IP bind command in Network part of the configuration:

    ################################## NETWORK #####################################

    # By default, if no "bind" configuration directive is specified, Redis listens
    ...
    #
    # Examples:
    #
    # bind 192.168.1.100 10.0.0.1
    # bind 127.0.0.1 ::1
    #
    # ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the
    ...
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    bind 127.0.0.1 ::1
    bind 127.0.0.1 192.168.10.10 // Add this