Laravel Reverb: WebSocket nativi per notifiche real-time senza dipendenze esterne
Fino a febbraio 2024, ogni volta che un cliente mi chiedeva funzionalità real-time in un'applicazione Laravel - notifiche push, aggiornamento live di dashboard, chat interna, editing collaborativo - avevo due opzioni, entrambe problematiche. La prima era Pusher, un servizio SaaS che gestisce i WebSocket al posto tuo: funziona bene, si integra con Laravel in cinque minuti, ma costa. Per un'applicazione con 200 utenti simultanei e 50.000 messaggi al giorno, il piano Pusher parte da 49 dollari al mese - e scala rapidamente verso i 99 o 199 dollari quando il traffico cresce. La seconda opzione era un server WebSocket self-hosted basato su Node.js (tipicamente laravel-websockets di BeyondCode o soketi): funziona, è gratuito, ma aggiunge una dipendenza esterna significativa - un runtime Node.js da mantenere, aggiornare e monitorare separatamente dal tuo stack PHP.
Laravel Reverb, rilasciato con Laravel 11 a marzo 2024, ha eliminato entrambi i compromessi. È un server WebSocket scritto in PHP, nativo nell'ecosistema Laravel, che gira come processo PHP persistente sul tuo stesso server senza richiedere Node.js, Pusher o qualsiasi altra dipendenza esterna. L'ho messo in produzione su un VPS Hetzner CPX21 (4 vCPU AMD, 8 GB RAM) per un'applicazione di collaborazione documentale con 200 utenti simultanei e circa 30.000 eventi WebSocket al giorno. Il server Reverb consuma 45 MB di RAM a regime, gestisce le connessioni con un event loop asincrono basato su ReactPHP, e non ha mostrato un singolo problema di stabilità in dieci mesi di produzione ininterrotta. Costo aggiuntivo rispetto all'infrastruttura esistente: zero.
Come funziona tecnicamente Reverb sotto il cofano?
Reverb è un server WebSocket PHP che gira come processo di lunga durata - non nel ciclo request/response di PHP-FPM, ma come daemon indipendente gestito da systemd. Internamente usa ReactPHP come event loop per gestire migliaia di connessioni simultanee su un singolo thread, senza il overhead del modello process-per-request di FPM. Quando un client JavaScript si connette via WebSocket, Reverb mantiene la connessione aperta e ascolta gli eventi pubblicati dall'applicazione Laravel attraverso il sistema di broadcasting nativo di Laravel.
Il flusso è questo: il tuo controller Laravel dispatcha un evento broadcast (ad esempio OrderStatusUpdated), Laravel lo pubblica su un canale Redis, Reverb riceve l'evento da Redis e lo inoltra a tutti i client WebSocket iscritti a quel canale. Il tutto avviene in 10-50 millisecondi dalla pubblicazione dell'evento alla ricezione sul browser dell'utente. Nessun polling, nessuna richiesta HTTP ripetuta, nessun ritardo.
L'installazione è un singolo comando:
# Installazione di Reverb (Laravel 11+)
php artisan install:broadcasting
# Questo comando installa Reverb, configura il .env con le credenziali,
# pubblica il file di configurazione config/reverb.php,
# e installa Laravel Echo nel frontendLa configurazione nel .env è minimale:
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=my-app
REVERB_APP_KEY=my-app-key
REVERB_APP_SECRET=my-app-secret
REVERB_HOST="0.0.0.0"
REVERB_PORT=8080
REVERB_SCHEME=httpIn produzione, Reverb gira dietro Nginx come reverse proxy che gestisce la terminazione TLS e il proxy delle connessioni WebSocket:
# Configurazione Nginx per il proxy WebSocket verso Reverb
# /etc/nginx/sites-available/app.conf
location /app {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeout WebSocket: mantieni la connessione aperta
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}Il parametro critico è proxy_read_timeout 86400s: senza di esso, Nginx chiude le connessioni WebSocket inattive dopo 60 secondi (il default), causando disconnessioni frequenti dei client. Con un timeout di 24 ore, la connessione resta aperta e Reverb gestisce i keepalive internamente con ping/pong WebSocket ogni 25 secondi.
L'evento broadcast e il listener JavaScript: il flusso completo
Per capire la potenza del modello, ecco un esempio completo: una notifica real-time che appare nell'interfaccia quando un ordine cambia stato. Lato backend, definisci un evento broadcast:
// app/Events/StatoOrdineAggiornato.php
class StatoOrdineAggiornato implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(
public readonly Ordine $ordine,
public readonly string $nuovoStato,
) {}
// Il canale è privato: solo l'utente proprietario dell'ordine lo riceve
public function broadcastOn(): array
{
return [
new PrivateChannel("ordini.{$this->ordine->cliente_id}"),
];
}
// Dati inviati al client JavaScript
public function broadcastWith(): array
{
return [
'ordine_id' => $this->ordine->id,
'riferimento' => $this->ordine->riferimento,
'stato' => $this->nuovoStato,
'aggiornato_alle' => now()->format('H:i'),
];
}
}Lato frontend, Laravel Echo si connette a Reverb e ascolta il canale:
// resources/js/bootstrap.js - configurazione Echo con Reverb
import Echo from 'laravel-echo';
import Pusher from 'pusher-js'; // Echo usa il protocollo Pusher
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT,
forceTLS: true,
enabledTransports: ['ws', 'wss'],
});
// Ascolta gli aggiornamenti sugli ordini del cliente corrente
Echo.private(`ordini.${userId}`)
.listen('StatoOrdineAggiornato', (evento) => {
// Mostra una notifica toast nell'interfaccia
mostraNotifica(
`Ordine ${evento.riferimento}: ${evento.stato}`,
evento.aggiornato_alle
);
});Quando il controller aggiorna lo stato dell'ordine e dispatcha l'evento (event(new StatoOrdineAggiornato($ordine, 'spedito'))), Reverb lo inoltra al browser del cliente in tempo reale. L'utente vede apparire la notifica senza ricaricare la pagina, senza polling, senza nessuna azione da parte sua. Il modello è lo stesso di Pusher - Laravel Echo usa il protocollo Pusher per la compatibilità - ma il server è tuo, gira sulla tua infrastruttura, e non costa nulla.
Canali privati e autorizzazione: la sicurezza che Pusher non ti dà gratis
Un aspetto che molti tutorial trascurano è l'autorizzazione sui canali WebSocket. Nel nostro esempio, il canale ordini.{cliente_id} è un canale privato: solo l'utente che possiede quegli ordini deve poterlo sottoscrivere. Con Pusher, l'autorizzazione viene delegata a un endpoint callback del tuo server - ma la configurazione è spesso lasciata debole o bypassata per "far funzionare il demo". Con Reverb, l'autorizzazione è nativa in Laravel e si configura nel file routes/channels.php con la stessa logica delle policy Eloquent:
// routes/channels.php - autorizzazione canali WebSocket
Broadcast::channel('ordini.{clienteId}', function (User $user, int $clienteId) {
// Solo il cliente proprietario o un admin possono sottoscrivere
return $user->cliente_id === $clienteId || $user->is_admin;
});Questa singola riga garantisce che un utente malintenzionato non possa sottoscrivere il canale di un altro cliente semplicemente cambiando l'ID nel JavaScript. Reverb verifica l'autorizzazione attraverso il middleware di autenticazione Laravel prima di permettere la sottoscrizione al canale privato. È un dettaglio di sicurezza fondamentale che distingue un'implementazione WebSocket professionale da un giocattolo - e nel mio lavoro di consulenza sulla sicurezza delle applicazioni web, le credenziali per valutare questi aspetti sono nel mio profilo professionale, ho visto troppi progetti dove i canali WebSocket erano completamente aperti perché "tanto è una funzionalità interna".
I canali presence vanno un passo oltre: non solo autorizzano l'accesso, ma notificano tutti i sottoscrittori quando un utente entra o esce dal canale. Nell'applicazione di collaborazione documentale, ho usato i canali presence per mostrare in tempo reale chi sta visualizzando lo stesso documento - una funzionalità simile a quella di Google Docs ("Mario sta modificando...") implementata con poche righe di codice Laravel e nessuna libreria JavaScript aggiuntiva. Ogni volta che un utente apre un documento, Echo lo iscrive al canale presence documento.{id}, e tutti gli altri utenti sullo stesso canale ricevono un evento here con la lista aggiornata dei partecipanti. L'intera funzionalità - backend e frontend - è circa 40 righe di codice PHP e 15 righe di JavaScript.
Deployment in produzione: systemd, monitoring e resilienza
Il processo Reverb deve essere gestito come un daemon di lunga durata. In produzione, lo configuro come servizio systemd con restart automatico:
# /etc/systemd/system/reverb.service
[Unit]
Description=Laravel Reverb WebSocket Server
After=network.target redis.service
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/php artisan reverb:start --host=127.0.0.1 --port=8080
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.targetCon Restart=always, se il processo Reverb crasha (cosa che in dieci mesi non è mai successa, ma la resilienza si progetta prima che serva), systemd lo riavvia in 5 secondi. I client Echo si riconnettono automaticamente grazie al meccanismo di reconnect built-in - l'utente vede al massimo un breve ritardo nelle notifiche, non un errore.
Il monitoring di Reverb si integra con lo stesso stack che uso per il resto dell'applicazione. Un health check ogni 30 secondi verifica che il processo sia attivo e che il WebSocket risponda ai ping:
# Health check per Reverb (da integrare nel monitoring)
curl -s -o /dev/null -w "%{http_code}" \
--max-time 5 \
http://127.0.0.1:8080/app/health || echo "REVERB_DOWN"Nel mio stack di monitoring, questo check è integrato con il sistema di alerting che ho descritto nell'articolo su Laravel Pulse per il monitoraggio applicativo: se Reverb non risponde per più di 60 secondi, una notifica Telegram avvisa il team. In dieci mesi, l'unica volta che Reverb ha avuto un problema è stato durante un aggiornamento PHP-FPM che ha accidentalmente ucciso anche il processo Reverb - systemd lo ha riavviato in 5 secondi e nessun utente se ne è accorto.
Scaling: quando un singolo processo non basta più
Reverb gestisce migliaia di connessioni simultanee su un singolo processo grazie all'event loop asincrono. Per l'applicazione di collaborazione con 200 utenti, il processo Reverb usa 45 MB di RAM e lo 0,3% della CPU - margini enormi prima di dover pensare allo scaling. Ma quando il numero di connessioni simultanee supera le 5.000-10.000, un singolo processo Reverb può diventare il collo di bottiglia. La soluzione è lo scaling orizzontale: più processi Reverb su più server, con Redis come backbone per la distribuzione degli eventi tra le istanze. Laravel supporta questo pattern nativamente - basta configurare REVERB_SCALING_ENABLED=true nel .env e aggiungere un secondo server con un'altra istanza di Reverb che legge dallo stesso Redis.
Un aspetto spesso sottovalutato dello scaling è il monitoraggio delle connessioni attive. Reverb espone metriche interne che puoi leggere via API per capire quante connessioni sono attive, quanti messaggi sono stati inviati e qual è la latenza media di delivery. Nel mio setup di produzione, un comando Artisan schedulato ogni minuto legge queste metriche e le salva nelle tabelle di Pulse, creando una timeline storica delle connessioni WebSocket che si sovrappone alle metriche applicative standard. Quando il numero di connessioni simultanee supera una soglia configurata (nel caso dell'applicazione di collaborazione, la soglia è 180 su un massimo teorico di 200), il sistema invia un alert preventivo che mi permette di valutare se è il momento di aggiungere un secondo worker Reverb prima di raggiungere la saturazione. Questa proattività nel monitoring è ciò che distingue un'infrastruttura che "regge fino a quando non crolla" da un'infrastruttura che "scala prima di averne bisogno" - lo stesso principio che applico nella gestione di qualsiasi VPS con applicazioni PHP in produzione e che ho descritto nell'articolo sull'osservabilità minima.
Per le PMI italiane, questo scenario di scaling è raro: la maggior parte delle applicazioni business interne (CRM, gestionali, portali B2B) ha meno di 500 utenti simultanei, un carico che Reverb gestisce senza sforzo su un VPS da 4 vCPU. Il vero vantaggio di Reverb per le PMI non è la scalabilità - è l'eliminazione di una dipendenza esterna (Pusher o Node.js) che riduce la superficie di manutenzione, abbassa i costi mensili, e semplifica il deployment. Se stai pagando Pusher 49-199 dollari al mese per le funzionalità real-time della tua applicazione Laravel, o se stai mantenendo un server Node.js separato solo per i WebSocket, la migrazione a Reverb è un intervento che si ripaga in uno o due mesi di canoni risparmiati. Nel mio profilo professionale trovi il dettaglio dell'esperienza su infrastrutture Laravel in produzione, inclusi i deployment con Reverb, Horizon e Pulse che compongono lo stack di monitoring e comunicazione real-time che installo come baseline su ogni nuovo progetto. Se vuoi valutare la migrazione della tua applicazione da Pusher o laravel-websockets a Reverb, contattami per una sessione di analisi: in due ore configuriamo Reverb, testiamo la latenza e verifichiamo la compatibilità con il tuo stack esistente.