Laravel WebSockets Server with Valet and SSL
Photo by Pavan Trikutam on Unsplash
Preamble
Thanks to Marcel Pociot et. al. for the awesome Laravel package. Thanks to DevMarketer for the step-by-step instructions.
Why, though?
So, I've wanted to try broadcasting/real-time notifications with Laravel Echo and Pusher for the longest time. But working mostly on internal applications that should not rely on external service I was SOL. After Marcel gave a great talk on "Understanding Laravel broadcasting" at Laracon Online Winter 2021 I was more than ready to plunge into the topic.
So I opened up a new terminal window, entered laravel new websockets
(for creating a fresh Laravel app), and followed the step-by-step instructions in the docs available at https://beyondco.de/docs/laravel-websockets/.
I don't know where I messed up but nothing worked as expected. Turns out, it was my local setup with Valet and its self-signed SSL certificates (it's 2021, who uses HTTP anymore, am I right?)
So here's my guide on how to run your own local WebSockets server with Valet and SSL.
Let's go!
Prerequisites
I'm running Laravel Valet 2.14.1 and PHP 8.0.3
Setup
Starting from scratch, let's create a brand new Laravel project (Laravel 8.36.2 at the time of writing)
$ laravel new websockets
Fresh SSL certificate, anyone?
$ cd websockets
$ valet secure
Restarting nginx...
The [websockets.test] site has been secured with a fresh TLS certificate.
Let's change the app's URL in .env
APP_URL=https://websockets.test
We need to pull in the beyondcode/laravel-websockets
package
composer require beyondcode/laravel-websockets
Now let's publish the migration (for statistics) and the config file
$ php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"
Let's publish the websockets config file:
$ php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"
Run the migrations (you should set up your database beforehand and update .env
accordingly)
$ php artisan migrate
Configuration
Now that we're ready to start, let's have a look at our config files. First, we need to change BROADCAST_DRIVER
in .env
...
BROADCAST_DRIVER=pusher
...
PUSHER_APP_ID=1 // this can be anything you want
PUSHER_APP_KEY=some-key // this can also be anything you want
PUSHER_APP_SECRET=some-secret // also this
PUSHER_APP_CLUSTER=mt1 // doesn't matter
"Why pusher
?" you may ask yourself. Don't I want to not use external services? You're right and we are NOT using pusher. Since laravel-websockets
uses the same API as pusher we're using the pusher
driver.
Now, in config/broadcasting.php
find connections
:
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => true,
'host' => '127.0.0.1', // add this
'port' => 6001, // and this
'encrypted' => true, // also this
'scheme' => 'https', // and this
'curl_options' => [ // since we're only doing stuff locally this is fine
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_SSL_VERIFYPEER => 0,
],
],
Almost done! Let's have a look at config/websockets.php
'ssl' => [
/*
* Path to local certificate file on filesystem. It must be a PEM encoded file which
* contains your certificate and private key. It can optionally contain the
* certificate chain of issuers. The private key also may be contained
* in a separate file specified by local_pk.
*/
'local_cert' => env('LARAVEL_WEBSOCKETS_SSL_LOCAL_CERT', null),
/*
* Path to local private key file on filesystem in case of separate files for
* certificate (local_cert) and private key.
*/
'local_pk' => env('LARAVEL_WEBSOCKETS_SSL_LOCAL_PK', null),
We need to set those environment variables in our .env
file for SSL to work.
In .env
add the following entries:
LARAVEL_WEBSOCKETS_SSL_LOCAL_CERT=/absolute/path/to/your/crt/file
LARAVEL_WEBSOCKETS_SSL_LOCAL_PK=/absolute/path/to/your/key/file
Depending on your setup, those files will reside in ~/.config/valet/Certifificates
, in my case it would look like this
LARAVEL_WEBSOCKETS_SSL_LOCAL_CERT=/Users/maze/.config/valet/Certificates/websockets.test.crt
LARAVEL_WEBSOCKETS_SSL_LOCAL_PK=/Users/maze/.config/valet/Certificates/websockets.test.key
Now we're ready to fire up the WebSockets server with
$ php artisan websockets:serve
If everything went right you should see the following output in your terminal
Starting the WebSocket server on port 6001...
Open up a browser and head to the WebSockets dashboard located at https://websockets.test/laravel-websockets
, and click on Connect
.
You should see a few events of type subscribed
which means that the server is running. Your terminal will also be flooded with debugging messages.
Awesome right? But wait, there's more! How about we connect the frontend to popup an alert window every time an event gets dispatched? This would be super annoying to users, but for the sake of the example good enough.
Frontend
We need to pull in two NPM packages
$ npm install --save-dev laravel-echo pusher-js
Again, a Pusher package? Well, yes, same API, remember?
Now let's setup Laravel Echo in resources/js/bootstrap.js
:
import Echo from 'laravel-echo';
window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
forceTLS: true,
wsHost: window.location.hostname,
wssPort: 6001,
});
Compile your frontend assets:
$ npm run dev
Since we're using a vanilla Laravel installation we'll just add some JS to the welcome view located in resources/views/welcome.blade.php
:
<script src="{{ mix('js/app.js') }}"></script>
<script>
Echo.channel('home')
.listen('Message', (e) => alert(e.message));
</script>
</body>
</html>
This will listen on the home
channel and will alert the message of the event that broadcasts to this channel. Speaking of events, we yet need to create one, so
$ php artisan make:event Message
app/Events/Message.php
should look like this
<?php
namespace App\Events;
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 Message implements ShouldBroadcast // this needs to be added
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message; // our message
public function __construct(string $message)
{
$this->message = $message;
}
public function broadcastOn()
{
return new Channel('home');
}
}
This event will broadcast on the home
channel after being dispatched, and Laravel Echo will listen for that event being dispatched.
Now, open a browser window and point it to the index page of our local application, located at https://websockets.test
, you should see the welcome page.
In your terminal open up Laravel Tinker. We're using Tinker to quickly dispatch an event without writing a whole lot of application logic.
$ php artisan tinker
Psy Shell v0.10.8 (PHP 8.0.3 — cli) by Justin Hileman
>>>
and fire the event by hand
$ php artisan tinker
Psy Shell v0.10.8 (PHP 8.0.3 — cli) by Justin Hileman
>>> App\Events\Message::dispatch('this is awesome')
Now when you press enter, the event will be dispatched and you should see an alert pop up in your browser. We're done! Wasn't that hard, wasn't it?
Where to go from here?
You can check out the code for this example at https://github.com/mazedlx/laravel-websockets-example.
Don't forget to have a look at Pusher and Ably, they both offer generous free plans.
Also, head over to https://github.com/beyondcode/laravel-websockets and star the repo and follow Marcel Pociot on Twitter.
If you want to see a real-life example, my good friend Christoph Rumpel wrote a blog post not too long ago covering real-time notifications using the Larvel Websockets package.
driesvints liked this article
Other articles you might like
How to add WebAuthn Passkeys To Backpack Admin Panel
Want to make your Laravel Backpack admin panel more secure with a unique login experience for your a...
Quickest way to setup PHP Environment (Laravel Herd + MySql)
Setting up a local development environment can be a time taking hassle—whether it's using Docker or...
Access Route Model-Bound Models with "#[RouteParameter]"
Introduction I've recently been using the new #[RouteParameter] attribute in Laravel, and I've been...
The Laravel portal for problem solving, knowledge sharing and community building.
The community