Why Real-Time Features Need a Different Approach
Traditional HTTP is request-response: the client asks, the server answers, and the connection closes. Real-time notifications — a new message arriving, an order status changing, a collaborator’s cursor moving — need the server to push information to the client without the client asking first. WebSockets solve this by keeping a persistent, bidirectional connection open, and Laravel has increasingly good first-party support for building on top of them.
Laravel Broadcasting: The Conceptual Model
Laravel’s broadcasting system doesn’t implement WebSocket infrastructure itself — it provides a clean abstraction for broadcasting events to a WebSocket service (Pusher, Ably, or a self-hosted solution like Laravel Reverb) that handles the actual persistent connections. Your application dispatches events as normal; if an event implements ShouldBroadcast, Laravel automatically pushes it to the configured broadcasting driver, which then delivers it to subscribed clients in real time.
Laravel Reverb: Self-Hosted WebSockets
Reverb, Laravel’s first-party WebSocket server, removes the dependency on a third-party service for teams that want to self-host. It’s built for Laravel specifically, integrates directly with the broadcasting system with minimal configuration, and scales horizontally when paired with Redis for cross-server message distribution. For teams already comfortable managing their own infrastructure, this eliminates a recurring third-party cost and an external dependency.
Channels: Public, Private, and Presence
Public channels broadcast to anyone listening — useful for things like a public live sports score update. Private channels require authorization, checked through a callback you define, appropriate for user-specific notifications where you need to verify the connecting user actually owns that data. Presence channels extend private channels with awareness of who else is currently subscribed — the foundation for features like “who’s currently viewing this document” or a live user list in a chat room.
Building a Notification Flow, End to End
A typical implementation: a database event (a new order is placed) triggers a Laravel event. That event implements ShouldBroadcast and specifies a private channel scoped to the relevant user, like PrivateChannel('orders.'.$order->user_id). On the frontend, Laravel Echo (a small JavaScript library) subscribes to that channel and listens for the specific event, updating the UI the moment the broadcast arrives — no polling, no manual refresh, and no delay beyond actual network latency.
Queueing Broadcast Events
Broadcasting an event involves a network call to your WebSocket driver, which shouldn’t block the main request-response cycle. Implementing ShouldBroadcast alongside ShouldQueue pushes the actual broadcast onto a queue, so the triggering request returns quickly regardless of any latency in the broadcasting service itself. This is a small detail that’s easy to overlook and meaningfully affects perceived application responsiveness under load.
Authorization for Private Channels
Channel authorization callbacks, defined in your broadcasting routes file, receive the authenticated user and the channel parameters, and return true or false for whether that user should be allowed to subscribe. This is a genuine security boundary — treat it with the same care as any other authorization check, since a mistake here can leak private data to users who shouldn’t see it, exactly the kind of subtle bug that’s easy to miss in casual testing but serious in production.
Handling Connection State on the Frontend
WebSocket connections can drop — network blips, server restarts, mobile devices switching networks. A production-quality real-time feature needs to handle reconnection gracefully, typically re-subscribing to previously joined channels automatically, and ideally reconciling any state that might have changed while disconnected by fetching a fresh snapshot rather than assuming the reconnected state picks up exactly where it left off.
Scaling Considerations
A single WebSocket server process can handle a meaningful number of concurrent connections, but scaling beyond one server requires a way for events triggered on one server to reach clients connected to a different server — this is where Redis pub/sub comes in, acting as a shared message bus that every WebSocket server instance subscribes to, ensuring a broadcast triggered anywhere reaches every relevant connected client regardless of which server they’re attached to.
When Not to Reach for WebSockets
Not every “real-time-ish” feature needs a persistent WebSocket connection. Data that updates every few minutes is often better served by simple polling, which is dramatically simpler to implement, debug, and scale. Reserve WebSockets for genuinely time-sensitive features — chat, live collaboration, live notifications — where the latency and server overhead of polling would be a real user-facing problem, not just a theoretical inefficiency.
Practical Recommendations
- Always queue broadcast events rather than sending them synchronously within the triggering request.
- Treat channel authorization as a genuine security boundary, and test it explicitly.
- Handle reconnection and state reconciliation on the frontend deliberately, not as an afterthought.
- Consider Laravel Reverb if you want to avoid a third-party WebSocket service dependency and are comfortable with the added operational responsibility.