Home / News / Real-Time Laravel APIs Using Laravel Reverb (WebSockets)

Real-Time Laravel APIs Using Laravel Reverb (WebSockets)

Here’s the sarcastic summary with hashtags:

Modern customers anticipate instant updates: new chat messages that don’t need to be refreshed, dashboards that keep up with reality, and notifications that appear when anything changes. Real-time UX is no longer a “nice to have”; it is required for SaaS, markets, analytics, collaboration tools, and other applications. Laravel Reverb is the perfect solution for any Laravel app that needs a first-party, high-performance WebSocket server, with built-in support for Laravel Echo. With Reverb, you can spin up a WebSocket server, broadcast Laravel events, and consume them in the browser with Laravel Echo for no vendor lock-in, no per-message fees, and tight Laravel integration. Reverb also supports remote origins, Queues, and SSL. Here’s a step-by-step guide on how to set up a real-time API using Laravel Reverb:

  • Install Reverb and Echo: <

    Modern customers anticipate instant updates—new chat messages that don’t need to be refreshed, dashboards that keep up with reality, and notifications that appear when anything changes. Real-time UX is no longer a “nice to have”; it is required for SaaS, markets, analytics, collaboration tools, and other applications.

    Laravel Reverb integrates a first-party, high-performance WebSocket server into your Laravel application. It enables you to broadcast Laravel events via WebSockets and consume them in the browser using Laravel Echo—no vendor lock-in, no per-message fees, and tight framework integration.

    Below is a simple, beginner-friendly guide to creating real-time APIs in Laravel using Reverb, including setup, code samples for notifications, chat, and dashboards, recommended practices, and a direct comparison to Pusher/Echo.

    What we’ll build (and why)

    By the end, you’ll be able to:

    • Spin up Reverb and connect a frontend via Laravel Echo.
    • Broadcast server events (orders updating, metrics changing).
    • Push live notifications, a realtime chat, and a live dashboard.
    • Avoid common pitfalls (origins, queues, SSL, scaling).

    Prerequisites

    • Laravel 11/12 project
    • Node + Vite (default in new Laravel apps)
    • Redis (recommended for scaling later)

    Install & configure Laravel Reverb

    Quick install

    php artisan install:broadcasting --reverb

    This scaffolds broadcasting, installs Reverb + Echo, and injects sensible .env variables.
    Prefer manual?

    composer require laravel/reverb && php artisan reverb:install

    also works.

    PHP is still highly relevant for building scalable, dynamic apps—see why PHP is a top choice for e-commerce development

    Essential environment variables

    Reverb identifies the app with credentials and needs host/port info:

    BROADCAST_CONNECTION=reverb
    
    REVERB_APP_ID=my-app-id
    REVERB_APP_KEY=my-app-key
    REVERB_APP_SECRET=my-app-secret
    
    -  Where Laravel sends broadcast messages (public entrypoint or proxy)
    REVERB_HOST=ws.example.com
    REVERB_PORT=443
    REVERB_SCHEME=https
    
    -  Where the Reverb server actually binds (internal)
    REVERB_SERVER_HOST=0.0.0.0
    REVERB_SERVER_PORT=8080
    

    Important distinction:REVERB_SERVER_HOST/PORT controls the Reverb process binding; REVERB_HOST/PORT tells Laravel where to send messages (often your public hostname through Nginx/ALB).

    Allowed Origins (CORS for sockets)

    Restrict which frontends may connect:

    // config/reverb.php
    'apps' => [[
      'app_id' => env('REVERB_APP_ID'),
      'allowed_origins' => ['https://app.example.com'], // or ['*'] during local dev
    ]],
    

    Requests from other origins will be rejected.

    Start Reverb

    php artisan reverb:start
    # options:
    # php artisan reverb:start --host=127.0.0.1 --port=9000
    

    The default is 0.0.0.0:8080. Use php artisan reverb:restart after code changes—Reverb is a long-running process.
    Using Herd/Valet locally with HTTPS? You can pass a hostname or a TLS cert so Reverb serves wss:// directly.

    Wire up the frontend with Laravel Echo

    Reverb uses the Pusher protocol, so the browser uses pusher-js under the hood with Echo’s reverb broadcaster.
    Install (already done if you ran the installer):
    npm i -D laravel-echo pusher-js
    Bootstrap Echo (e.g., ‘resources/js/bootstrap.js`):

    `
    import Echo from ‘laravel-echo’
    import Pusher from ‘pusher-js’
    window.Pusher = 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 ?? 80,
    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? ‘https’) === ‘https’,
    enabledTransports: [‘ws’, ‘wss’],
    })
    `

    The reverb broadcaster requires laravel-echo v1.16.0+.
    Build assets:
    npm run build # or npm run dev

    Broadcasting 101 (server side)

    Create a broadcastable event

    `
    <?php

    namespace App\Events;

    use Illuminate\Broadcasting\Channel;
    use Illuminate\Broadcasting\PrivateChannel; // for private channels
    use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
    use Illuminate\Queue\SerializesModels;

    class OrderShipmentStatusUpdated implements ShouldBroadcast
    {
    use SerializesModels;

    public function __construct(
        public int $orderId,
        public string $status,
    ) {}
    
    public function broadcastOn(): Channel|array
    {
        return new PrivateChannel("orders.{$this->orderId}");
    }
    
    public function broadcastAs(): string
    {
        return 'OrderShipmentStatusUpdated';
    }
    
    public function broadcastWith(): array
    {
        return ['orderId' => $this->orderId, 'status' => $this->status];
    }
    

    }
    `

    Authorize channels

    routes/channels.php controls who can subscribe:

    `
    use Illuminate\Support\Facades\Broadcast;
    use App\Models\User;

    Broadcast::channel(‘orders.{orderId}’, function (User $user, int $orderId) {
    // Only owners (or staff) can listen
    return $user->orders()->whereKey($orderId)->exists();
    });
    `

    Emit the event

    use App\Events\OrderShipmentStatusUpdated;
    event(new OrderShipmentStatusUpdated($orderId, 'shipped'));

    Don’t forget your queue worker. Broadcasting is queued so it doesn’t slow down responses. Make sure a worker is running (e.g., php artisan queue:work).

    Receive events in the browser


    // Private channel requires auth (handled by Laravel)
    Echo.private(
    orders.${orderId})
    .listen('.OrderShipmentStatusUpdated', (e) => {
    console.log(e.status) // 'shipped'
    })

    The leading dot '.EventName' is used when you set a custom broadcastAs.

    Real-time feature recipes

    Live notifications

    Server: use Laravel Notifications with the broadcast channel.

    `
    use Illuminate\Notifications\Notification;

    class NewMessageNotification extends Notification
    {
    public function via($notifiable): array
    {
    return [‘database’, ‘broadcast’];
    }

    public function toBroadcast($notifiable): array
    {
        return ['message' => 'You have a new message!'];
    }
    

    }
    `

    Client:


    Echo.private(
    App.Models.User.${userId})
    .notification((n) => {
    // render toast/badge/center
    console.log(n.message)
    })

    Notifications over Echo use the user’s private channel automatically.

    Realtime chat (private & presence)

    Authorize a presence channel to know who’s online and show “typing…”:


    // routes/channels.php
    Broadcast::channel('chat.{roomId}', function ($user, int $roomId) {
    // return user info to other members
    return ['id' => $user->id, 'name' => $user->name];
    });

    Frontend:


    // Presence channel
    const channel = Echo.join(
    chat.${roomId}`)
    .here(users => renderOnline(users))
    .joining(user => addOnline(user))
    .leaving(user => removeOnline(user))

    // Typing indicator using client events (no round-trip)
    channel.whisper(‘typing’, { name: me.name })
    channel.listenForWhisper(‘typing’, (e) => showTyping(e.name))
    `

    (With Pusher, enabling client events in your Pusher app is required; Reverb handles whispering with Echo locally.)
    Broadcast messages from the server:

    `
    class MessagePosted implements ShouldBroadcast
    {
    public function __construct(public int $roomId, public array $message) {}

    public function broadcastOn(): array
    {
        return [new \Illuminate\Broadcasting\PresenceChannel("chat.{$this->roomId}")];
    }
    

    }
    `

    Listen in the browser:


    Echo.join(
    chat.${roomId})
    .listen('MessagePosted', (e) =>
    appendMessage(e.message))

    Streaming dashboard metrics

    Backend:

    `
    class MetricUpdated implements ShouldBroadcast
    {
    public function __construct(public string $key, public float $value) {}

    public function broadcastOn(): array
    {
        return [new \Illuminate\Broadcasting\Channel('metrics')]; // public
    }
    

    }
    `

    Client:


    Echo.channel('metrics')
    .listen('MetricUpdated', ({ key, value }) => updateChart(key, value))

    Learn more about PHP & It’s Trending Frameworks

    Production checklist & best practices

    Run Reverb under a process manager Supervisor/systemd) and increase minfds so it can open enough file descriptors for connections.
    [supervisord]
    minfds=10000

    • Reverse proxy & SSL. Terminate TLS at Nginx/ALB and proxy to REVERB_SERVER_HOST:REVERB_SERVER_PORT. Use wss:// in production.
    • Allowed origins. Lock down allowed_origins in config/reverb.php to your app domains.
    • Queues are mandatory. Keep a reliable queue worker online (Horizon is great) so broadcasts are sent promptly.
    • Don’t leak secrets. Treat WebSocket payloads like API responses. Never broadcast sensitive data; use private/presence channels with robust auth.
    • Shape payloads intentionally. Use broadcastWith() to send just what the UI needs; use broadcastAs() to control event names.
    • Restart on deploy. Reverb is long-running. Call php artisan reverb:restart during deployments for zero-downtime restarts.
    • Scale horizontally with Redis. Set REVERB_SCALING_ENABLED=true and run Reverb on multiple instances behind a load balancer; Reverb uses Redis pub/sub to fan out messages.

    Common pitfalls (and how to fix them)

    • “Connected, but no events arrive.”
      The queue worker isn’t running or is failing. Check queue:work logs.
    • CORS/Origin errors.
      Your frontend origin isn’t in allowed_origins. Add the domain or * for local dev.
    • Port/host confusion.
      REVERB_SERVER_* is where the process binds; REVERB_HOST/PORT is the public address Echo connects to.
    • Mixed content (WS vs WSS).
      On HTTPS sites you must use secure WebSockets (wss://) or the browser will block the connection.
    • Event name mismatch.
      If you set broadcastAs(), listen with a leading dot: .YourEventName.

    Reverb vs Pusher vs Echo (what’s the difference?)

    Tool What it is Pros Cons Best for
    Laravel Reverb First-party WebSocket server you host No vendor fees; deep Laravel integration; easy install; Redis-backed horizontal scaling You manage infrastructure and uptime Apps wanting control & predictable cost
    Pusher Channels Managed WebSocket service Zero ops; global infrastructure; great tooling Usage-based cost; external dependency Teams prioritizing speed of launch
    Ably (Pusher-compatible) Managed realtime platform Global low-latency; rich features Cost; external dependency High-scale global apps
    Laravel Echo Client library, not a server Unified API in JS (works with Reverb/Pusher/Ably) N/A—used with one of the above Every Laravel frontend

    Reverb and Pusher/Ably are server/broker choices. Echo is the browser client you’ll use with any of them. Reverb uses the Pusher protocol, so Echo’s Pusher adapter works seamlessly with the reverb broadcaster.

    Advanced: presence, client events, and model broadcasting

    • Presence channels return member info in the channel auth callback and let you show “who’s online” and online counts.
    • Client events (aka “whispers”) are great for ephemeral UX like typing indicators—sent peer-to-peer via the channel without hitting your Laravel backend.
    • Model broadcasting lets you stream changes to Eloquent models with Echo hooks (useEchoModel) for a delightful DX in React/Vue.

    Horizontal scaling in a sentence

    Flip REVERB_SCALING_ENABLED=true, point all Reverb instances at the same Redis, run reverb:start on multiple servers, put them behind a load balancer. Done.

    Conclusion

    Laravel Reverb makes real-time APIs feel like Laravel: events you already use become live updates in the browser; Echo provides a clean, chainable API; and you can self-host or scale out with Redis as demand grows. With the patterns mentioned above—notifications, chat, and live dashboards—you can provide the kind of UX that people increasingly expect.

Tagged:

Leave a Reply

Your email address will not be published. Required fields are marked *