Skip to content

Broadcasting

Larastvel supports real-time event broadcasting via WebSocket and third-party services.

Drivers

DriverDescription
NativeSelf-hosted WebSocket server
Reverb DBNative broadcaster with database-backed cross-instance scaling (Laravel 13 scaling.driver = database)
PusherPusher Channels
AblyAbly Realtime
LogLog broadcaster for debugging

Native Broadcaster

rust
use larastvel_core::axum::Extension;
use larastvel_core::broadcasting::{ws_handler, NativeBroadcaster, SubscriberRegistry};

// Create the registry and broadcaster (name + registry)
let registry = SubscriberRegistry::new();
let broadcaster = NativeBroadcaster::new("native", registry.clone());

// Register WebSocket route
router.ws("/ws", ws_handler);

// Attach the registry to the final router via the Application
app.with_layer(|router| router.layer(Extension(registry)));

Reverb Database Scaling

Laravel 13's Reverb database driver lets multiple WebSocket server instances coordinate through the database instead of Redis. In Larastvel, ReverbDatabaseBroadcaster delivers to its own subscribers immediately and publishes to a shared reverb_scaling table; every other instance polls that table and replays new messages to its own clients.

rust
use larastvel_core::broadcasting::{ReverbDatabaseBroadcaster, ReverbScalingStore};
use std::sync::Arc;
use std::time::Duration;

// Shared store (one database, many instances)
let store = ReverbScalingStore::new(db.clone());
store.ensure_table_exists().await?;

// Instance A: accepts broadcasts
let broadcaster_a = Arc::new(ReverbDatabaseBroadcaster::new(
    "native",
    registry.clone(),
    store.clone(),
));

// Instance B: polls the store for messages published elsewhere
let broadcaster_b = Arc::new(ReverbDatabaseBroadcaster::new(
    "native",
    other_registry.clone(),
    store,
));
let poller = broadcaster_b.spawn_scaling_poller(Duration::from_millis(50));
// ... stop with `poller.abort()` or run inline via `run_scaling_poller(...)`

Broadcasts from any instance are fanned out to all instances' connected clients. Consumed messages are marked sent_at in the reverb_scaling table (ReverbScalingStore::pending_count() reports un-consumed rows, and drain_pending() polls once manually).

Broadcast Manager

rust
use larastvel_core::broadcasting::log::LogBroadcaster;

let mut manager = BroadcastManager::new("native");
manager.register("native", NativeBroadcaster::new("native", registry));
manager.register("log", LogBroadcaster::new("log"));

Broadcasting Events

The manager broadcasts events that implement the BroadcastEvent trait. Use the #[broadcast_event] macro — the struct must provide a channels() method. See the full reference for details.

rust
use larastvel_core::broadcast_event;
use larastvel_core::broadcasting::Channel;
use serde::Serialize;

#[broadcast_event("order.shipped")]
#[derive(Debug, Serialize)]
struct OrderShipped {
    order_id: String,
}

impl OrderShipped {
    fn channels(&self) -> Vec<Channel> {
        vec![Channel::public("orders")]
    }
}

Broadcast through the manager (the default broadcaster):

rust
let event = OrderShipped { order_id: "123".to_string() };
manager.broadcast(&event).await?;

Or implement BroadcastEvent manually for full control:

rust
use larastvel_core::async_trait;
use larastvel_core::broadcasting::{BroadcastEvent, Channel};

#[derive(Debug, serde::Serialize)]
struct OrderShipped {
    order_id: String,
}

#[async_trait]
impl BroadcastEvent for OrderShipped {
    fn broadcast_event_name(&self) -> &str {
        "order.shipped"
    }

    fn broadcast_channels(&self) -> Vec<Channel> {
        vec![Channel::public("orders")]
    }
}

A raw BroadcastMessage is the payload sent to clients — broadcast it via a Broadcaster directly:

rust
use larastvel_core::broadcasting::BroadcastMessage;

let message = BroadcastMessage::new(
    "order.shipped",
    serde_json::json!({"order_id": "123"}),
    vec!["orders".to_string()],
);

let broadcaster = manager.default_broadcaster()?;
broadcaster.broadcast(message).await?;

Channels

TypeDescription
Channel::Public(name)Accessible to anyone
Channel::Private(name)Requires authentication
Channel::Presence { name, channel_data }Tracks connected users
rust
let channel = Channel::Private("orders.42".to_string());

Released under the MIT License.