HOWTO How to setup and scale the perfect websocket server for your Laravel project

16 min read

More and more web developers are using Laravel as their Backend Framework – and not without reason: Laravel has become the most popular PHP framework in the last years. The advantages are apparent: web projects can be shipped extremely fast and are easy to operate, and its growing community is eager to provide top-notch packages that are easy to integrate. However, things are getting a bit more complicated regarding WebSockets. If you want to incorporate real-time functions into your web application via WebSockets – for example, to display a live count of visitors on your website or to send live notifications, the selection of the proper setup is atypically complicated for Laravel. It seems that the requirements of Laravel users are too individual to offer a standardized solution.

ABOUT YOU has addressed this issue and searched for a successful WebSocket server solution for their own e-commerce suite. If you want to learn more about possible WebSocket backends for Laravel and how to use them successfully in your web project, stay tuned!

tl;dr

With Laravel and the appropriate WebSocket server, it is easily possible to push event-based data from a Laravel application to the web application. However, when choosing the right Websocket server solution, you should carefully consider which functionalities each option offers and which one is best suited for your intended use. 

The PHP-based Laravel WebSockets Server with good documentation and a large user base is recommended for simple push notifications. If, on the other hand, you plan with high utilization of your web application and need maximum availability, you will not be able to avoid a horizontally scalable solution. 

It is worth looking at the still very new but promising Soketi project, or at Laravel WebSockets v2, which is already very stable even if still in a beta version.

 

Pushing instead of polling?

At ABOUT YOU, we initially relied on a polling solution in which the client requested an API at regular intervals to determine whether there were any new messages. However, we quickly realized that this solution was reaching its limits because it put an unnecessary load on our servers.

Therefore, going with a WebSocket-based solution instead of polling was an easy decision for us. Instead of constantly asking for new messages, a WebSocket connection is opened only once and a notification is then broadcasted to the front-end from the server as soon as a new message is available.

How to find the right websocket backend for Laravel

For us, at ABOUT YOU, it was essential to have a WebSocket solution that is easy to integrate and maintain for our current Infrastructure. If you also want to find a suitable WebSocket backend for Laravel, you should first get an overview of which solutions match with your purpose. 
Since multiple solutions are available, we first evaluated the most commonly used ones before going with one of them. See the list below to find the pros and cons of each possible implementation. 

NameTechnologyLicenseActualityScalabilityProtocol
Laravel Echo ServerNodeJSMITmediumhighsocket.io
Laravel WebsocketsPHPMIThighok, v2-betapusher
SoketiC, NodeJSMITmediumtoppusher
Pusher.comSaascommercialhightoppusher
AblySaascommercialhightoppusher

Laravel Echo Server 

The Laravel Echo Server is based on NodeJS and the popular websocket.io library. It was the first recommended WebSocket server implementation for Laravel. The `laravel-echo-server` runs, as one would expect for NodeJS, very fast and stable. However, the project is currently only being maintained by a handful of volunteers, resulting in some legacy issues accumulating over time. For example, the last version (1.6.2, by the time of writing this article) of the Laravel Echo Server, which was released in May 2020 does not work with the latest Socket.io client libraries and lacks security and stability patches. Even as a Docker container, the project is cumbersome to operate and scale. A small plus point: horizontal scaling via Redis is generally supported. But the future development of Laravel Echo Server seems to be unsure.

Soketi

The open-source WebSocket backend soketi is still relatively new on the market. It is built on top of the fast μWebSockets backend, a NodeJS port of the original C library. Soketi implements the Pusher-Protocol, which is supported by Laravel out-of-the-box. In addition to its modern structure, the WebSocket server can also be optimally operated and scaled in a Docker container. Monitoring the soketi server with Prometheus and numerous database backends is also supported. 

“When developing soketi, I relied on the websocket library µWebSockets.js - one of the fastest websocket implementations that has been in use in many productive environments for years. Compared to other websocket backends, soketi doesn't only run with Laravel. It can be used with any framework that is compatible with the pusher protocol.” 

Alex Renoki, Founder of soketi

Laravel Websockets

With over 4k stars on Github, Laravel WebSockets is currently the most popular WebSocket solution for Laravel – and rightly so. It is ideal for developers working with WebSockets for the first time or for those who want to be up and running in no time. Laravel WebSockets is the only available solution that natively runs on PHP and can therefore sit right next to your Laravel application. 

In addition to the highly stable and resource-saving WebSocket server based on the popular PHP React library, Laravel WebSockets includes a practical debug dashboard. The dashboard allows you to quickly test and debug any event.

What is the catch? Laravel WebSockets is currently available in a stable version, `1.x` and a `2.x` beta version. The `2.x` version is a complete rewrite that offers the possibility of scaling the WebSocket server horizontally. Nothing stands in the way of operating several WebSocket servers with a Redis server as a shared data store. It is all the more regrettable that the `2.x` version has so far only been available as a beta version.

Interview with Marcel Pociot, founder of Laravel Websockets

1) Hey Marcel, you and your company beyondcode are the brains behind the most popular PHP websocket implementation for Laravel, often simply referred to as Laravel websockets. How did you come to develop the package and release it for the first time at the end of 2018?

Marcel: At the time Laravel Websockets was created, I was doing a lot of work with ReactPHP - a library for asynchronous PHP programming. I wanted to build an easy to run PHP WebSocket server. The socket.io protocol was originally intended to be used for the implementation. Unfortunately, there is very little documentation on this protocol, so I switched to the better documented pusher protocol. As a side effect, you can now use all official pusher client SDKs with Laravel websockets.

2) The current stable version 1.x of Laravel Websockets is easy to install, but does not scale well. Version 2.0, which can be, for example scaled horizontally with a Redis DB, has only been available as a beta version for a very long time. Do you already have a planned release date for version 2.0?

Marcel: Unfortunately, I cannot give an exact release date for version 2.0. Laravel WebSockets is used on a wide variety of Linux distributions and PHP versions and it is important to me that users can rely on it to function properly. In the 2.x beta version, there have always been some bugs that have delayed the release. I am confident that we will publish a new stable release at the beginning of 2022, which, among other things, will bring better scalability.

3) What do you think are the biggest challenges when building real-time capabilities with Laravel? 

Marcel: I think that with Laravel Echo on the client side and pushers, Socket.io, and Laravel WebSockets we already have a very good basis for developing real-time functions with Laravel on various scales. For me, the biggest challenges are in the operation of the Websocket server. Do you prefer a SaaS solution or would you prefer to host the server yourself? Is it just a matter of building a few live functions into an existing Laravel app or do many simultaneous connections have to be supported? I can only recommend that you think about the respective purpose in advance. And in most cases, starting with Laravel Websockets is a good idea.

4) What do you recommend to users who are working with Laravel and Websockets for the first time. Are there any good resources or tricks?

Marcel: My tip is to think about whether you really need a websocket server and the added complexity. For simple applications, polling may even be sufficient to implement "real-time features". In addition, I can only recommend looking at the Laravel documentation on the subject and just experimenting with WebSockets. I am currently working on a new video course that will appear at the same time as the 2.0 release of Laravel Websockets. 

Setup in a few steps

At ABOUT YOU, we had three essential requirements for our future WebSocket backend: It should be stable, easily configurable and run reliably even under heavy load. After weighing the advantages and disadvantages of the different solutions, we chose Laravel Web Sockets and their `2.x` beta version. It was a breeze to set up our multi-tenant application to run on one single WebSocket Server and furthermore, it can be easily scaled horizontally without fear of leaking information from one tenant to another.

Configuring and starting Laravel websockets

If you also want to work with Laravel WebSockets, you will find step-by-step instructions for configuration here. Laravel WebSockets and the Pusher SDK in version 5.0 can be easily installed in the respective Laravel project using the following composer commands: 

composer require beyondcode/laravel-websockets "^2.0"
composer require pusher/pusher-php-server "^5.0"

We are about to install version 2.0 of Laravel Websockets, because we want to use Redis as the backend and for the horizontal scalability of the Websocket server. 

Now we have to prepare the DB migrations for proper operation with the following command:

php artisan vendor:publish --provider ="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"

Then we carry out the DB migrations with the following command:

php artisan migrate

In the background, the necessary tables for the debug dashboard of the WebSocket server are created in the Laravel database. Now we have the WebSocket server in the file `config/websockets.php` configured:

// config/websockets.php
​
return [
    // ...
​
    'apps' => [
        [
            'id' => env('PUSHER_ID', 'test'),
            'name' => env('APP_NAME', 'test'),
            'host' => env('PUSHER_HOST', 'test.test'),
            'key' => env('PUSHER_KEY', 'test'),
            'secret' => env('PUSHER_SECRET'),
            'path' => env('PUSHER_PATH'),
            'capacity' => null,
            'enable_client_messages' => false,
            'enable_statistics' => true,
            'allowed_origins' => [
                env('LARAVEL_WEBSOCKETS_DOMAIN'),
            ],
        ],
    ],
​
    'replication' => [
        'mode' => env('WEBSOCKETS_REPLICATION_MODE', 'redis'),
        'modes' => [
            // ...
​
            'redis' => [
                'connection' => env('WEBSOCKETS_REDIS_REPLICATION_CONNECTION', 'websocket'),
                'channel_manager' => App\Service\WebSockets\RedisChannelManager::class,
​
            // ...
        ],
​
    // ...
];

For a successful WebSocket server configuration the environment variables PUSHER_ID, PUSHER_HOST, PUSHER_KEY, and PUSHER_SECRET must be used correctly. If you plan to run your WebSocket Server on a separate domain, the environment variable LARAVEL_WEBSOCKETS_DOMAIN is essential to avoid any CORS (Cross-Origin-Resource-Sharing) issues.

Example environment variables in .env

BROADCAST_DRIVER=pusher
WEBSOCKETS_REPLICATION_MODE=redis
PUSHER_ID=app
PUSHER_HOST=app.test
PUSHER_SCHEME=http
PUSHER_KEY=app
PUSHER_SECRET=80f5dd30-c24e-4088-87ae-d57d0059f109

Once configured, the WebSocket server can be started with the following command:

php artisan websockets:serve
INFO: The websocket protocol (ws: // and wss: //)
In contrast to the HTTP protocol, the WebSocket runs bidirectional communication via the WS (unencrypted) or WSS protocol (encrypted). In addition to the web application, which usually returns data from the webserver via HTTP on request, a parallel connection to the WebSocket server is established via a WebSocket client (for example, with the pusher Javascript client, recognizable by the WS:// protocol). The web application can then receive and process data event-based via this second channel. The server automatically notifies the logged-in clients when any new information is available. Such WebSocket connections are, for example, the basis for almost all live chat applications or presence displays, as this type of data transfer is incredibly effective (data is only transferred if something has changed).

Setup Event-Broadcasting

After the WebSocket server is up and running, the question arises about getting the Laravel app to send data from the server-side Laravel application – so-called broadcasting events – to the client via WebSocket. 

However, as mentioned before, this setup is a breeze, as the Laravel Framework ships with built-in support for communications using a push server.

We next configure the previously installed Laravel WebSockets server in the `config/broadcasting.php` configuration file.

// config/broadcasting.php
​
return [
    // ...
​
    'default' => env('BROADCAST_DRIVER', 'pusher'),
​
    'connections' => [
        'pusher' => [
            'driver' => 'pusher',
            'key' => env('PUSHER_KEY'),
            'secret' => env('PUSHER_SECRET'),
            'app_id' => env('PUSHER_ID'),
            'options' => [
                'host' => env('PUSHER_PUSH_HOST', 'api'),
                'port' => env('PUSHER_PUSH_PORT', 6001),
                'scheme' => env('PUSHER_PUSH_SCHEME', 'http'),
                'encrypted' => true,
            ],
        ],
​
        // ...
    ],
​
    // ...
];

Ensure that the configured pusher host matches the value set in `config/websocket.php` since the application must communicate with the pusher server using the same hostname. In a Docker-based setup, for example, we would choose the name of the Docker container here. 

By the way, as is usual with Laravel, all variables should be defined in the .env file and not written into the config file. The depicted second parameters of the env command are only default values ​​if the corresponding environment variable is not defined.

The app ID must correspond to the app ID used later in the pusher client.

If broadcasting is configured for Laravel, we still have to activate the broadcasting service provider in the Laravel project. We can do this by commenting out the appropriate line in `config/app.php`:

// config/app.php
​
return [
    // ...
​
    'providers' => [
        //...
​
        // Enable default broadcast service provider
        App\Providers\BroadcastServiceProvider::class,
​
        // ...
    ],
​
    // ...
];

Sending Events

Now that broadcasting has been set up in Laravel, and the WebSocket server has been configured, we can start sending the first events from within the Laravel application. Fortunately, this sounds more complicated than it is.

At ABOUT YOU, for example, we have a "BackgroundJobUpdated" event, which, as the name already tells, is dispatched once a task running in the background was updated (for example generating a CSV export). The event is used to notify any user about the current progress of the background job. You will find out how to do this now. 

Create event with ShouldBroadcast

To broadcast an event via WebSocket to the client, we only need to extend our Event classes with the ShouldBroadcast class, provided by Laravel. Laravel handles the rest automatically. If, as in our case, you want to ensure that the event does not go to all users but only to a specific logged-in user, you can define this in the `broadcastOn` method. In our case, we use the `user_id` from the `BackgroundJob` object to only send the event to the private channel of the user with the specific user ID (for example, `App.Models.User.2348792`). 

// app/Events/BackgroundJob.php
​
use App\Models\BackgroundJob;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
​
class BackgroundJobUpdated implements ShouldBroadcast
{
    public function __construct(
        public BackgroundJob $backgroundJob
    ) {}
​
    public function broadcastOn()
    {
        return new PrivateChannel('App.Models.User.' . $this->backgroundJob->user_id);
    }
​
    // ...
}

Triggering the broadcast event

At the appropriate point in the application at which the broadcast event is to be sent to the user, this can be triggered with one of the event methods. In the following example, the event is triggered with event and a new BackgroundJobUpdated event with the background job object from Eloquent is transferred as a parameter as a payload. 

$backgroundJob = App\Models\BackgroundJob::first();
​
$backgroundJob->successful_count++;
$backgroundJob->save();
​
event(new App\Events\BackgroundJobUpdated($backgroundJob));

If the WebSocket connection is configured correctly, this data is immediately pushed to the connected WebSocket clients. 

Receiving events

Whoever says A must also say B – whoever sends must also receive. This is where the counterpart to the Laravel WebSocket server comes into play – the Laravel echo Javascript client. This is a lean Javascript library that can be operated either with the socket.io or pusher Javascript client. We use the echo client with the pusher Javascript client in our example. 

The echo client and the pusher library are installed in the web front end (for example, a small SPA with NuxtJS) with the following commands: 

yarn add laravel-echo pusher-js

After that, a WebSocket connection to the Laravel WebSocket server can be established with a few method calls: 

const echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.PUSHER_KEY,
    wsHost: process.env.PUSHER_HOST,
    wsPort: 443,
    forceTLS: true,
})
​
echo.private(`App.Models.User.${currentUser.id}`)
    .listen('BackgroundJobUpdated', event => console.log(event));

After initializing the Echo client, we can easily define channels we want to listen to for new events. In the example above, we just log the output of a `BackgroundJobUpdated` event to the console once the backend dispatched it to the current user.

Authentication and private channels

In principle, every client can first log into the Laravel WebSocket server. However, the WebSocket client must authenticate itself when connecting to receive private messages. 

For this purpose, Laravel WebSockets provides the route/broadcasting/auth out-of-the-box. The Laravel echo client also automatically tries to authenticate the WebSocket user to use private channels when the connection is established. 

This can be defined in the boot method of the BroadcastServiceProvider. 

// app/Providers/BroadcastServiceProvider.php
public function boot()
{
    Broadcast::routes();
​
    require base_path('routes/channels.php');
}

Channels can, for example, be private channels such as the channel for updating user data. We use the following configuration to ensure that a logged-in user only has access to the broadcast channel with his user data and the updates intended for him. In this case, the user ID at the end of the channel name must correspond to the ID of the logged-in user - simple but effective!

// routes/channels.php
​
use Illuminate\Support\Facades\Broadcast;

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

Optional tips

Apps & Multi-Tenant capability

The specialty of the SCAYLE Commerce Suite is that we have built the application to be multi-tenant-capable. That means, depending on which customer is logged in, the database belonging to the customer must be addressed. Of course, the WebSocket server must understand this and handle it accordingly. 

Laravel WebSockets offers the concept of apps here. A WebSocket server can be used for several apps at the same time. By default, only one app is configured, which is sufficient for most cases – for example, if you want to implement a few push notifications via WebSocket for a small Laravel application. But you could also run a WebSocket server for several Laravel applications, for example. 

In our case, we use Laravel WebSockets app management to provide the multi-tenant capability of our application. When the connection is established, the logged-in user is automatically mapped to the tenant that is suitable for him. This is made possible by an individual ConfigAppManager class.

// app/Services/WebSockets/ConfigAppManager.php
​
use App\Models\Client;
use BeyondCode\LaravelWebSockets\Apps\ConfigAppManager as BaseManager;
​
class ConfigAppManager extends BaseManager
{
    public function __construct()
    {
        parent::__construct();
​
        $this->apps = Client::all()->map(function ($client) {
            return [
                'id' => $client->key,
                'name' => $client->name,
                'host' => Str::after($client->host, '//'),
                'key' => $client->key,
                'secret' => $client->secret,
                'capacity' => null,
                'enable_client_messages' => false,
                'enable_statistics' => false,
                'allowed_origins' => [],
            ];
        });
    }
}

We get all tenants (we call them clients) from the database and return them to the Laravel WebSocket server. Each client, therefore, has a specific key for later connection establishment and assignment to the tenant.

To use the custom app manager, we just have to replace the default reference with our own in `config/websockets.php`:

// config/websockets.php
​
return [
    // ...
​
    'managers' => [​
        'app' => App\Service\WebSockets\ConfigAppManager::class,
    ],
​
    // ...
];

SSL and reverse proxy 

Safety first! Just like with HTTP, you should always secure your WebSocket server with SSL in live operation. A regular web server such as Nginx or Apache can act as a reverse proxy. The reverse proxy takes over the SSL termination and encrypts the transmitted data between client and server. Since the WebSocket server uses a different protocol, we can configure it to run on the same domain as the webserver. For example, we run our Laravel project on https://aboutyou.tech – this means that our WebSocket server traffic can simply run on wss://aboutyou.tech and thus on the same domain and through the same reverse proxy.

And this wraps up our overview of Laravel websockets and their setup. By the way, we are actively recruiting for multiple for PHP roles and looking forward to receiving your application for Senior Backend PHP Developer (m/f/d), Software Engineer PHP (m/f/d), or any of the many other openings on our Jobs page!