Skip to Content
Course content

216: Laravel Broadcasting and Echo

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years back, I was working on a logistics dashboard for a client. We had this "Order Status" screen where dispatchers monitored shipments. The problem was that the dispatchers were obsessively hitting F5 every ten seconds because they were terrified of missing a "Delayed" or "Cancelled" update. It was a nightmare for the database—hundreds of redundant requests per minute—and a frustrating experience for the users. I remember telling the lead dev, "We're treating the web like it's 1998." That was the day we ripped out the polling logic and implemented broadcasting. The moment we saw the status badge flip from 'Processing' to 'Shipped' in real-time without a page refresh, the entire mood in the room changed. That's the power of moving from a "Pull" architecture to a "Push" architecture.

Turning the Server into a Broadcaster

In a standard Laravel app, your server is reactive; it waits for a request and then sends a response. Broadcasting flips this. It allows your server to push a message to a client-side listener via a WebSocket. To make this happen, you don't just fire a standard event; you create an event that implements the ShouldBroadcast interface. When Laravel sees that interface, it knows that after the event is handled internally, it also needs to be pushed out to a broadcasting driver—like Pusher, Soketi, or Laravel Reverb.

The most important thing to remember here is that broadcasting is almost always handled by your queue. You don't want your user's HTTP request to hang while your server is trying to talk to a WebSocket server. I've seen developers wonder why their real-time updates aren't appearing, only to realize their queue:work process wasn't running. If you aren't using a queue, you're blocking your application's execution flow, which defeats the purpose of a snappy, real-time UI.


namespace App\Events;

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

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

    public function __construct(public Order $order) {}

    public function broadcastOn(): array
    {
        // We use a private channel so only the customer/admin can see this order
        return [
            new PrivateChannel('orders.' . $this->order->id),
        ];
    }

    public function broadcastWith(): array
    {
        // Only send the data the frontend actually needs
        return [
            'status' => $this->order->status,
            'updated_at' => $this->order->updated_at->toDateTimeString(),
        ];
    }
}

Listening with Laravel Echo

Now that the server is shouting into the void, you need something on the frontend to actually listen. That's where Laravel Echo comes in. Echo isn't the WebSocket server itself; it's a JavaScript wrapper that makes subscribing to channels incredibly intuitive. Instead of dealing with raw WebSocket frames, you use a fluent API to listen for specific events.

You'll encounter three main types of channels. Public channels are for everyone (like a live sports score). Private channels require authentication—which Laravel handles via a channels.php route file—ensuring that User A can't listen to User B's private order updates. Then there are Presence channels, which are essentially private channels that also let you know who else is currently subscribed (perfect for that "User is typing..." indicator in a chat app).


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,
    forceTLS: true
});

window.Echo.private(`orders.${orderId}`)
    .listen('OrderStatusUpdated', (e) => {
        console.log('Order status changed to: ' + e.status);
        // Here you'd update your DOM or trigger a toast notification
        updateOrderStatusUI(e.status);
    });

Securing the Stream

One common mistake I see is developers leaving their channels public because they find the authorization step tedious. If you use a PrivateChannel, you must define the authorization logic in routes/channels.php. This is essentially a middleware for your WebSockets. If the callback returns true, the connection is allowed; if it returns false, the user is blocked.

I always recommend being as strict as possible here. Check not only if the user is authenticated, but if they actually own the resource they are trying to listen to. It's an easy vector for data leaks if you simply check Auth::check() without verifying the resource ID.


Broadcast::channel('orders.{orderId}', function ($user, $orderId) {
    return (int) $user->id === (int) \App\Models\Order::find($orderId)->user_id;
});



📋 Practical Task

Exercise: Building a Real-Time Order Status Tracker

Your goal is to implement a real-time update system for a fictional e-commerce store. You will need to build the backend event and the frontend listener to ensure the user sees their order status change without refreshing the page.

  • The Event: Create a ShipmentTracked event that implements ShouldBroadcast. This event should accept a Shipment model and broadcast on a PrivateChannel named shipments.{shipmentId}.
  • The Authorization: In routes/channels.php, write the authorization logic to ensure that only the user who owns the shipment can subscribe to that specific channel.
  • The Payload: Override the broadcastWith method in your event to send only the current_location and the estimated_arrival properties of the shipment.
  • The Frontend: Write a Laravel Echo script that subscribes to the private shipment channel and logs the current_location to the browser console whenever the ShipmentTracked event is received.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.