Some time ago, I had the pleasure of developing a realtime app for my client. The app was based on Laravel, Node.js, Redis and Laravel Echo. During the process of development, I found a few things that can be not so obvious to developers who just started to tackle the problem of creating a realtime app with Laravel. In this post I'll share my insights.
Before reading the FAQ, familiarize yourself with the basic concepts of the Laravel Event Broadcasting.
Even though the post is grouped by questions, I recommend reading it from cover to cover. The following questions are answered in this post:
- Why do I prefer Laravel Echo Server to Pusher?
- What to set in host settings of Laravel Echo Server?
- Can I run Laravel Echo Server as root?
- How to make Laravel Echo Server work through HTTPS?
- How to use Let's Encrypt keys in Laravel Echo Server?
- Do I need a password for Redis?
- How to close the Redis port for external connections?
- How to make Laravel Echo work on a multilingual site?
- What if your app uses HTTP Authentication?
- How to manage Laravel Echo Server process with Supervisor?
- What to do if the supervisor service is not started automatically?
- Do I need Laravel Queue for my realtime app?
- How to not hardcode the hostname of Laravel Echo Server on client?
- How to properly include Socket.IO client library?
Why do I prefer Laravel Echo Server to Pusher?
Briefly, because of the Pusher's limitations. Pusher limits the number of simultaneous connections. If it's not critical for your app, Pusher is a good option. It's not our case, though.
Pusher is an external service, so we should take into account network latency which can slow down the response and impair the overall user experience.
What to set in host settings of Laravel Echo Server?
It is not obvious that Laravel Echo Server requires a hostname for the application host setting (host) and an absolute url for the authentication host setting (authHost) . Here is what is expected:
{
"authHost": "[laravel app url]",
"authEndpoint": "/broadcasting/auth",
"databaseConfig": {
"redis": {
"host": "[redis hostname]",
},
},
"host": "[echo server hostname]"
}
The [laravel app url] is your absolute Laravel application url without trailing slash (app.url). Usually It's something like https://example.com The [redis hostname] is "localhost" when you run your Redis server on the same machine where your Laravel Echo Server is located. The [echo server hostname] host should be accessible from the outside and it should not be "localhost".
Can I run Laravel Echo Server as root?
Yes, you can, but it's not recommended for security reasons.
How to make Laravel Echo Server work through HTTPS?
Assuming you have already enabled HTTPS on your main web server and obtained SSL keys, you need to set the protocol and paths for your SSL keys in the laravel-echo-config.json and here is how:
{
"protocol": "https",
"sslCertPath": "/home/[your user]/letsencrypt/etc/live/[your hostname]/fullchain.pem",
"sslKeyPath": "/home/[your user]/letsencrypt/etc/live/[your hostname]/privkey.pem"
}
The Let's Encrypt keys is not a requirement and provided just for example.
How to use Let's Encrypt keys in Laravel Echo Server?
The problem is that the default Let's Encrypt configuration pushes you to run it as root user (for example through sudo) and stores keys in /etc/letsencrypt folder which is not accessible to other users. The trick is simple, you need to run letsencrypt.sh on behalf of a regular user and put keys in a folder which is accessible to that user. It's possible. Letsencrypt tool has a parameter, called --config-dir, which is responsible for where to store keys and configuration. I managed to achieve the goal by using certonly mode of Letsencrypt with Nginx:
letsencrypt certonly -a webroot --webroot-path=[site document root] --agree-tos --email [your email] --config-dir="/home/[user]/letsencrypt/etc" --domains [site domain]
Do I need a password for Redis?
It'd be wise to set a strong password if Redis port is open for public access. If you set a password for Redis, don't forget to change your app configs accordingly. Laravel (.env):
REDIS_PASSWORD=[strong password]
Laravel Echo Server (laravel-echo-server.json):
{
"databaseConfig": {
"redis": {
"port": "6379",
"host": "localhost",
"password": "[strong password]"
}
}
}
How to close the Redis port for external connections?
To prevent brute-force attacks, you may want to close the Redis port for eternal connections through a firewall, for example ufw, and additionally bind Redis to the specific IP only:
bind 127.0.0.1
Read more in the Security section of the Redis documentation.
How to make Laravel Echo work on a multilingual site?
Laravel Echo Server can only communicate with Laravel authentication service if it's accessible directly. Where "directly" means without any redirects. So, make sure you have disabled all redirects including language redirects for the broadcasting/auth route. Read more in my article.
What if your app uses HTTP Authentication?
When a project is at an early development stage, the developers might want to hide it from public eyes by enabling HTTP Authentication. If it's your case, you need to include authentication credentials in the absolute url of the Laravel auth service:
{
"authHost": "https://[username]:[password]@[hostname]",
}
How to manage Laravel Echo Server process with Supervisor?
Imagine that you're in the country without Internet access. You server is restarted by your hoster after a regular software update. After the restart, your Laravel Echo Server is down and the chat is not working. Users are unhappy and you don't even know about the problem.
To avoid such situation, you may want to restart the Laravel Echo Server automatically after system restart or in case of failure. There are a few ways but I personally prefer Supervisor. You may learn more about Supervisor in the Laravel documentation or google about it. And I'll just provide a simple supervisor config for Laravel Echo Server which is run on behalf of a regular user (/etc/supervisor/conf.d/laravel-echo.conf):
[program:laravel-echo]
directory=[application root]
process_name=%(program_name)s_%(process_num)02d
command=/usr/bin/laravel-echo-server start
autostart=true
autorestart=true
user=[username]
numprocs=1
redirect_stderr=true
stdout_logfile=[application root]/echo.log
Where [application root] is your application root directory, and [username] is the name of a regular user.
What to do if the supervisor service is not started automatically?
I don't know why, but supervisor service is not enabled by default after installation on Ubuntu. Make sure it's working:
sudo service supervisor status
If not, enable it:
sudo systemctl enable supervisor
Do I need Laravel Queue for my realtime app?
Yes, you do. Laravel Queue is necessary for event broadcasting, so make sure you have run it through command line or supervisor. Find more in the official documentation for Laravel Queues.
How to not hardcode the hostname of Laravel Echo Server on client?
If your main site shares it domain name with Laravel Echo Server you may freely set the hostname from current url in the Laravel Echo initialization code on client:
window.Echo = new Echo({
broadcaster: 'socket.io',
host: 'https://' + window.location.hostname + ':6001'
});
This approach is especially handy when you have multiple environments, such as Dev, Staging and Production. If you want to make the initialization code even better, read the laravel-echo-server.json file via webpack at the build stage and set host property accordingly. It's just an idea, though, which I didn't try myself.
How to properly include Socket.IO client library?
Until we updated the official documentation "Driver Prerequisites - Socket.IO", it had contained the following recommendation on how to include Socket.IO client library into your application:
<script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>
In case of following this recommendation, you would use Socket.io library from Laravel Echo Server, which would become a single point of failure for the entire application. Guessed why? Because if Laravel Echo Server stops functioning for some reason, your JS code will crash with the following error:
Uncaught ReferenceError: io is not defined
But calm down. No need to throw Laravel Echo into the rubbish bin. We just need to think ahead and make your app tolerant to a Laravel Echo Server failure.
Here are a few solutions:
- Copy Socket.io Client lib to your public/js folder and add it to your main page template:
<script src="{{ asset('js/socket.io.min.js') }}"></script>
You may copy the lib through Webpack if you wish. Just make sure you're using the correct version.
- Always check if io object exists before using Echo:
if (typeof io !== 'undefined') {
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ':6001'
});
}
- Add Socket.io Client lib as a dependency and import it through require()
npm install socket.io-client --save-dev
import Echo from "laravel-echo"
window.io = require('socket.io-client');
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ':6001'
});
It's up to you which way you prefer but I wouldn't recommend consuming Socket.IO client library from Laravel Echo Server by link.
Conclusion
Laravel Broadcasting is an awesome feature which can simplify development of realtime applications. In our era of reactive and realtime apps, it's definitely worth learning and developing with it.
Developers can choose between Laravel Echo Server and Pusher options. Pusher can save time for development. However, it has limitations and it's not free. Laravel Echo Server option in its turn is relatively complex to setup at first glance, but it gives you more flexibility and allows you to save your money and users' time in the future.
Not all realtime apps are chats and their conditions can be different, so I encourage you to try both options, compare them in your application context and share your experience with the community.