Introduction

This is the guide of setting up a Docker-based Laravel project with WebSockets and Pusher with $0 cost using a Soketi package. Join me on this journey to learn how to leverage the power of Soketi and Docker in creating efficient and cost-effective Laravel applications with real-time communication capabilities.

It doesn't matter whether you're using Laravel Sail or a custom Docker setup, integrating Web Sockets into your Laravel application will be done in the same way through a “docker-composer.yml” file.

Keep in mind, that you can use Soketi without Docker, but in this tutorial, we will focus on setting up Soketi with Docker for a more streamlined development environment. You can follow the official documentation for more details of how to install Soketi without Docker if you prefer that approach.

Furthermore, keep in mind that Soketi is not specific to Laravel or PHP and can be used with other frameworks and languages as well. But, this article will specifically focus on using Soketi with Laravel and Laravel Echo. Let's discuss, what exactly Soketi is and why it's beneficial for your Laravel application.

What is Soketi

Soketi is a simple, fast, resilient, and Pusher-compatible open-source WebSockets server. It allows real-time communication between clients and servers, making it ideal for applications that require instant updates or event-driven functionality.

Soketi running in the terminal

Soketi is a free, fast, and easy to use JavaScript package that facilitates the implementation of WebSockets in any application. With Soketi, you can replace the Pusher service in your Laravel application and set up your own WebSockets server, giving you full control and eliminating any dependency on third-party services.

Why do you need to pay for a service like Pusher when you can run your own WebSockets server in your Docker container using Soketi for free? Let's see what we are going to master in this article.

What are we building?

To demonstrate the power of WebSockets with Soketi, Pusher, and Laravel Echo, we will create a simple app that will notify (in real-time) every logged-in user in real-time when someone creates a new account.

The only purpose of this app is to showcase the real-time communication capabilities of Soketi and Laravel Echo. We will use Laravel's authentication system to handle user login and registration. To achieve this, we will start by setting up a Laravel application using Docker.

Setting Up the Project

For this tutorial, we agreed to use Laravel within Docker containers, so the first step is to set up Laravel Sail. Laravel Sail is not necessary, it's just easier for me to spin up a new Laravel project with Docker.

Here is the link to the GitHub repo where you can find the complete code for this tutorial.

If you already have an existing project that you want to add Soketi to, you can skip this step and proceed to the "Install Laravel Echo and Pusher" section.

Install a new Laravel project

I'm going to go ahead and run the following command to install a new Laravel project:

laravel new register-notifier

If you don't have a Laravel Installer on your machine, please, follow the official Laravel's documentation for how to install the installer.

I don't need any starter kits or presets, so I will simply create a new Laravel project with the name "register-notifier". Once the Laravel project is installed, I can install Laravel Sail.

Install Laravel Sail

Laravel Sail is just a composer package that can be installed by a composer command. Just go into the created project and run the command:

composer require laravel/sail --dev

If you don't have Composer, you can download it from the official web page.

After installing Laravel Sail, we need to initialize a docker-composer.yml file by running this Artisan command:

php artisan sail:install

The list of services will appear in the terminal, where I need to choose those which I'm going to use in my project.

Output after running "php artisan sail:install" which shows the list of services you can choose from the list like mysql, pgsql, mariadb, redis, memcached, soketi and more

I'll pick "mysql" and "soketi" and hit "Enter". This is what the "soketi" service looks like in the "docker-composer.yml" file:

    soketi:
        image: 'quay.io/soketi/soketi:latest-16-alpine'
        environment:
            SOKETI_DEBUG: '${SOKETI_DEBUG:-1}'
            SOKETI_METRICS_SERVER_PORT: '9601'
            SOKETI_DEFAULT_APP_ID: '${PUSHER_APP_ID}'
            SOKETI_DEFAULT_APP_KEY: '${PUSHER_APP_KEY}'
            SOKETI_DEFAULT_APP_SECRET: '${PUSHER_APP_SECRET}'
        ports:
            - '${PUSHER_PORT:-6001}:6001'
            - '${PUSHER_METRICS_PORT:-9601}:9601'
        networks:
            - sail

You can paste this code in your docker-compose.yml file to set up the Soketi service in your project if you already have a project set up. Just keep in mind that you have to make sure that Laravel service and Soketi are on the same network to allow for communication between them.

In the example above, the network is called "sail", but in your case it might be something else. Moreover, make sure these ports that Soketi uses are available in your environment.

Install Laravel Echo and Pusher

Laravel Echo is a great package that allows us to easily implement real-time communication between the server and the client using WebSockets. Let's run Docker containers to run Yarn or NPM commands from inside.

sail up -d & sail shell

I'm running a sail up -d  command, but in your case it might be something else, depending on your project. We just need to run the containers and enter the container with PHP and Node isntalled.

Laravel Echo can be installed along with Pusher by running the following NPM command:

npm install --save-dev laravel-echo pusher-js

Or we can use the Yarn:

yarn add laravel-echo pusher-js --dev

We will also need a pusher library on the server side. We can require it by running this composer command:

composer require pusher/pusher-php-server

Echo and Pusher are installed, we can now set up the project boilerplate so that we have something to work with.

Building the Boilerplate

Okay, if I visit http://localhost in my browser, I see the default Laravel Welcome page.

Laravel's default welcome page

I want to create authentication scaffolding using Laravel's built-in authentication system. To create authentication scaffolding, I need to first install it with the following command:

composer require laravel/ui

When it's done installing, we can choose different front-end presets like "bootstrap", "vue" or "react" to generate the necessary files. I'll just use the "bootstrap" front-end preset for now because I'm not really concerned about front-end.

php artisan ui bootstrap --auth && npm install && npm run build

Great! Now, I have a good, old Laravel startup application with basic authentication scaffolding that all Laravel developers are familiar with.

Laravel's login page with bootstrap styling

I will also modify the DatabaseSeeder class to create 2 dummy users for future purposes.

<?php

namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        User::factory()->create(['email' => 'user1@mail.com']);
        User::factory()->create(['email' => 'user2@mail.com']);
    }
}

And run php artisan migrate --seed to create tables and seed the database with users so that I can log in with "user1@mail.com" user for testing purposes.

If you get an error saying something like SQLSTATE[HY000] [1044] Access denied for user 'sail'@'%' to database 'register_notifier' (Connection: mysql, SQL: select table_name as `name`, (data_length + index_length) as `size`, table_comment as `comment`, engine as `engine`, table_collation as `collation` from information_schema.tables where table_schema = 'register_notifier' and table_type in ('BASE TABLE', 'SYSTEM VERSIONED') order by table_name), try to run sail down -v and then sail up -d again. If you enter the container again and run php artisan migrate --seed it will work.

Preparing the Frontend

For the frontend, when we are logged in, we want to see a real-time alert message when any other user registers a new account. To achieve this, we don't need to modify anything in the frontend boilerplate generated by Laravel's authentication scaffolding.

Instead, we will utilize Laravel Echo and Pusher to handle the real-time communication. I'm going to open a resources/js/bootstrap.js file to set up Laravel Echo with Pusher. In my case, I just uncommented the code that is already present in the file, which sets up Laravel Echo with Pusher.

import Echo from 'laravel-echo'
import Pusher from 'pusher-js'

window.Pusher = Pusher

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: import.meta.env.VITE_PUSHER_APP_KEY,
    cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1',
    wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
    wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
    wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
})

Instead of recompiling all the JavaScript files, I'm going to just run yarn dev to make Vite watch any changes and automatically apply all necessary code transformations. In my case, I'll run sail yarn dev since I'm using Laravel Sail.

Now, with Laravel and the necessary authentication scaffolding in place, we need to ask Laravel Echo to listen for events related to user registration. We haven't created any backend yet to handle the registration event, we'll do it in a second, let's finish with JavaScript first.

I only have app.js and bootstrap.js files, so I'll open app.js and tell Laravel Echo to listen for UserRegistered event:

import './bootstrap'

window.Echo
    .channel('user-register')
    .listen('UserRegistered', ({ name }) => {
        alert(`User ${name} has registered!`)
    })

That's the whole JavaScript part that we need to do to prepare the frontend for real-time communication with Laravel Echo and Pusher. Let's prepare the backend now.

If you want to read more about Laravel, I have a very popular article "Abstracting External Services With Interfaces in Laravel Apps", where I go into detail about abstracting external services like Twilio in Laravel applications.

Preparing the Backend

For the backend, we have to create an Event and dispatch it when a user registers a new account. But before we do that, we need to set up Laravel to use Web Sockets for real-time communication.

Set up the .env file

As for our .env file, we need to set a BROADCAST_DRIVER variable to "pusher" and add the necessary Pusher credentials for real-time communication.

BROADCAST_DRIVER=pusher

PUSHER_APP_ID=app-id
PUSHER_APP_KEY=app-key
PUSHER_APP_SECRET=app-secret
PUSHER_HOST=soketi
PUSHER_PORT=6001
PUSHER_SCHEME=http
PUSHER_APP_CLUSTER=mt1

VITE_APP_NAME="${APP_NAME}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST=localhost
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

You can just copy and paste these values into your .env file, since these variables will be the same in all Docker-based environments with Soketi integration. "app-id", "app-key", "app-secret" are all correct values for the Soketi integration.

Uncomment BroadcastServiceProvider

The next step will be to uncomment the line in your config/app.php file that registers the BroadcastServiceProvider. This Service Provider is responsible for registering the necessary event listeners and broadcasting routes for Laravel Echo.

Create an event

Let's run the Artisan command to create an event that will be dispatched when a user registers a new account:

php artisan make:event UserRegistered

When we open the UserRegistered class created by the Artisan command, we can see that it's not implementing the ShouldBroadcast interface that is required for broadcasting the event over Web Sockets.

Let's fix it by adding the ShouldBroadcast interface to the UserRegistered class definition. This is what my UserRegistered.php file looks after making the necessary changes:

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class UserRegistered implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(public string $name)
    {
    }

    /**
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new Channel('user-register'),
        ];
    }
}

I've added a user's name to the constructor and changed the channel from PrivateChannel to Channel, since we aren't concerned about private channel broadcasting for this event.

Dispatch an event

To dispatch the event when a new user is registered is simple in Laravel because we can just define a method called "registered" in the RegisterController class. This method will be called when a user successfully registers a new account.

use App\Events\UserRegistered;
use Illuminate\Http\Request;

class RegisterController extends Controller
{
    // ...

    /**
     * The user has been registered.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $user
     * @return mixed
     */
    protected function registered(Request $request, $user)
    {
        event(new UserRegistered($user->name));
    }
}

Don't forget to import UserRegistered and Request classes at the top. That's all we needed to do to prepare the backend for real-time communication with Laravel Echo and Pusher. Let's just make sure that everything works as expected.

Final Manual Testing

To test if the setup for real-time communication with Laravel Echo and Pusher is working correctly, we need to perform some manual testing. Firstly, make sure that JavaScript is compiled with the Laravel Echo code that we added earlier.

Secondly, make sure that you defined the "registered" method in RegisterController that we did in the previous step.

To test that our real-time communication is working, I'll open 3 browser windows like this:

3 browser windows side to sides

I'll use the Brave browser (left one) to register a new user, Firefox (middle one) to login as "user1@mail.com", and Firefox Developer Edition (right one) to login as "user2@mail.com". When I hit the button "Register" in Brave, I get this:

3 browser windows side to sides with javascript alert fired

It works perfectly for me, and I hope it works for you as well when you perform the manual testing. If you have any issues or questions, don't hesitate to ask for help in the comments below, and I will answer as soon as possible because I'll get notified on my phone when there is a new comment.

I'm pretty sure that it will work perfectly for fresh projects, but existing projects may require additional configuration and troubleshooting to ensure seamless integration with Laravel Echo and Pusher.

Because sometimes Soketi ports might not be available or there could be conflicts with other WebSocket services running in Docker, it is important to carefully configure and troubleshoot the setup to avoid any issues or conflicts.

Source code on GitHub

Refer to the source code for more information that is available on GitHub. Each step from this article is mirrored on GitHub in commits, so you can easily navigate through the changes made during the setup process.

If you encounter any issues with the implementation, then the source code will be a helpful resource to refer to for troubleshooting and debugging.

If you are eager to learn more about Laravel and PHP, I highly recommend using Laracasts. I've been watching Laracasts for a while, and it has greatly improved my understanding of Laravel and PHP development.

Conclusion

In conclusion, setting up free Web Sockets with Laravel using the Soketi package and Docker is a cost-effective solution for real-time communication in your application that requires instant updates and notifications.

Plus, I like that Soketi's documentation is apparent and easy to follow, making it a great choice for programmers looking to implement real-time communication in their Laravel projects.

With the steps outlined in this guide, you should now have a fully functional setup for real-time communication using Laravel Echo and Pusher, allowing you to seamlessly integrate this feature into your application.

Remember to test thoroughly and refer to the source code on GitHub for troubleshooting and debugging if you encounter any issues during the implementation.

If you have any questions or encounter any difficulties, feel free to ask for help in the comments, and I'll do my best to assist you as fast as possible. Just don't forget to return to check the reply on your comment after some time.

Good luck with your real-time communication implementation, and happy coding!

Keywords: pusher, websocket, socket, event