laravel-websocket

Auto-invoked skill

Build real-time features with Laravel Reverb

Trigger Keywords

This skill automatically activates when Claude detects these keywords:

websocket real-time Reverb broadcast Echo pusher

Overview

The laravel-websocket skill provides expertise for building real-time features with Laravel Reverb. It covers WebSocket setup, broadcasting events, presence channels, and client-side integration with Laravel Echo.

What This Skill Provides

  • Reverb Setup - First-party WebSocket server configuration
  • Broadcasting - Event broadcasting to channels
  • Channel Types - Public, private, and presence channels
  • Laravel Echo - Client-side WebSocket integration
  • Scaling - Redis pub/sub for horizontal scaling
  • Authentication - Private channel authorization

Example Conversations

# Setting up Reverb
"Set up Laravel Reverb for real-time notifications"

# Broadcasting events
"Create a broadcast event when a new message is sent"

# Presence channels
"Show who's online in a chat room using presence channels"

# Client integration
"How do I listen for events with Laravel Echo?"

Broadcast Event Example

<?php

namespace App\Events;

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

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

    public function __construct(
        public Message $message
    ) {}

    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('chat.' . $this->message->conversation_id),
        ];
    }

    public function broadcastWith(): array
    {
        return [
            'id' => $this->message->id,
            'body' => $this->message->body,
            'user' => $this->message->user->only('id', 'name', 'avatar'),
            'created_at' => $this->message->created_at->toISOString(),
        ];
    }

    public function broadcastAs(): string
    {
        return 'message.sent';
    }
}

Channel Authorization

// routes/channels.php

// Private channel - user can only access their own
Broadcast::channel('orders.{orderId}', function ($user, $orderId) {
    return $user->id === Order::find($orderId)?->user_id;
});

// Presence channel - returns user data for "who's here"
Broadcast::channel('chat.{conversationId}', function ($user, $conversationId) {
    if ($user->canAccessConversation($conversationId)) {
        return [
            'id' => $user->id,
            'name' => $user->name,
            'avatar' => $user->avatar_url,
        ];
    }
});

Laravel Echo Client

// 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,
    wssPort: import.meta.env.VITE_REVERB_PORT,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});

// Listening to events
Echo.private(`chat.${conversationId}`)
    .listen('.message.sent', (event) => {
        console.log('New message:', event);
        messages.push(event);
    });

// Presence channel - track who's online
Echo.join(`chat.${conversationId}`)
    .here((users) => {
        onlineUsers = users;
    })
    .joining((user) => {
        onlineUsers.push(user);
    })
    .leaving((user) => {
        onlineUsers = onlineUsers.filter(u => u.id !== user.id);
    });

Channel Types

Type Use Case Auth Required
Channel Public data (scores, prices) No
PrivateChannel User-specific data (notifications) Yes
PresenceChannel Who's online (chat rooms) Yes + user data

Common Pitfalls

  • Missing queue worker - Broadcasts require php artisan queue:work
  • CORS issues - Configure config/cors.php for WebSocket connections
  • Auth endpoint - Ensure /broadcasting/auth route exists
  • Event naming - Use broadcastAs() for consistent event names

Scaling with Redis

// config/broadcasting.php
'reverb' => [
    'driver' => 'reverb',
    'key' => env('REVERB_APP_KEY'),
    'secret' => env('REVERB_APP_SECRET'),
    'app_id' => env('REVERB_APP_ID'),
    'options' => [
        'host' => env('REVERB_HOST'),
        'port' => env('REVERB_PORT', 443),
        'scheme' => env('REVERB_SCHEME', 'https'),
        'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
    ],
    'client_options' => [],
],

// For horizontal scaling, use Redis
// .env
BROADCAST_DRIVER=reverb
REVERB_SCALING_ENABLED=true

Testing WebSockets

use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Broadcast;

it('broadcasts message sent event', function () {
    Event::fake();

    $message = Message::factory()->create();

    event(new MessageSent($message));

    Event::assertDispatched(MessageSent::class, function ($event) use ($message) {
        return $event->message->id === $message->id;
    });
});

it('authorizes private channel access', function () {
    $user = User::factory()->create();
    $conversation = Conversation::factory()->create();
    $conversation->users()->attach($user);

    $this->actingAs($user)
        ->post('/broadcasting/auth', [
            'channel_name' => "private-chat.{$conversation->id}",
        ])
        ->assertOk();
});

Related Commands

Related Agent