WebSocket vs Server-Sent Events: Choosing the Right Real-Time Pattern for Your App

  • Автор темы Автор темы Sascha
  • Дата начала Дата начала

Sascha

Команда форума
Администратор
Ofline
3otvb2z646ytpt1hl2rv.jpg


There's a moment in every web developer's career when a client says, "Can we make this update in real time?" — and you realize polling every five seconds isn't going to cut it anymore. That moment is when you need to make a clear-headed decision: WebSocket or Server-Sent Events (SSE)? Both solve real-time communication, but they do it differently, and picking the wrong one creates architectural headaches down the road.

This article breaks down both technologies with practical code examples, real trade-offs, and clear guidance on when to use each.

What Problem Are We Actually Solving?​


Traditional HTTP is a request-response protocol. The browser asks, the server answers, the connection closes. For live dashboards, notifications, collaborative tools, or live feeds, this model falls apart. You need the server to push data to the client without waiting for a request.

Two mature solutions exist for this:

  • WebSocket — a full-duplex, persistent TCP connection
  • Server-Sent Events (SSE) — a unidirectional, HTTP-based stream from server to client

WebSocket: Full-Duplex Communication​


WebSocket upgrades an HTTP connection into a persistent, bidirectional channel. Both the client and server can send messages at any time. This is what powers chat apps, multiplayer games, and collaborative editors.

How WebSocket Works​


Код:
// Client-side WebSocket
const socket = new WebSocket('wss://yourapp.com/ws');

socket.addEventListener('open', () => {
  console.log('Connected');
  socket.send(JSON.stringify({ type: 'subscribe', channel: 'orders' }));
});

socket.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  console.log('Received:', data);
});

socket.addEventListener('close', () => {
  console.log('Disconnected — consider reconnect logic here');
});



On the Laravel side, using Laravel Reverb (the official first-party WebSocket server), you can broadcast events directly:


Код:
// app/Events/OrderStatusUpdated.php
class OrderStatusUpdated implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(public Order $order) {}

    public function broadcastOn(): array
    {
        return [new PrivateChannel('orders.' . $this->order->id)];
    }

    public function broadcastWith(): array
    {
        return [
            'status' => $this->order->status,
            'updated_at' => $this->order->updated_at->toISOString(),
        ];
    }
}

// Dispatch from a controller or job
OrderStatusUpdated::dispatch($order);



This integrates cleanly with Laravel Echo on the frontend for channel subscriptions and authentication.

WebSocket Trade-offs​


Pros:

  • True bidirectional — client can also send data
  • Low latency, minimal overhead after handshake
  • Ideal for high-frequency updates

Cons:

  • Requires a persistent connection server (Reverb, Pusher, Soketi)
  • More complex infrastructure — doesn't work behind some proxies without configuration
  • Stateful connections are harder to scale horizontally without sticky sessions or a pub/sub broker like Redis

Server-Sent Events: Simple, Underrated, Powerful​


SSE is HTTP-based streaming. The server keeps the connection open and pushes text/event-stream formatted data. The client can't send messages back through this channel — it's one-way, server to client.

That sounds limiting, but for a huge class of problems — notifications, live feeds, progress bars, log streaming — it's exactly what you need, with far less infrastructure overhead.

SSE in Laravel​


Код:
// routes/web.php
Route::get('/stream/notifications', function () {
    return response()->stream(function () {
        $i = 0;
        while (true) {
            $notifications = Auth::user()
                ->unreadNotifications()
                ->since(now()->subSeconds(5))
                ->get();

            foreach ($notifications as $notification) {
                echo "data: " . json_encode($notification->toArray()) . "\n\n";
                ob_flush();
                flush();
            }

            if (connection_aborted()) break;
            sleep(3);
        }
    }, 200, [
        'Content-Type' => 'text/event-stream',
        'Cache-Control' => 'no-cache',
        'X-Accel-Buffering' => 'no', // Important for Nginx
    ]);
})->middleware('auth');



Код:
// Client-side SSE
const eventSource = new EventSource('/stream/notifications');

eventSource.onmessage = (event) => {
  const notification = JSON.parse(event.data);
  displayNotification(notification);
};

eventSource.onerror = () => {
  // Browser auto-reconnects by default
  console.warn('SSE connection lost, retrying...');
};



Note the X-Accel-Buffering: no header — without it, Nginx will buffer your stream and your "real-time" events will arrive in batches. This is a common production gotcha.

You can also use named events for finer control:


Код:
echo "event: order_update\n";
echo "data: " . json_encode(['id' => 42, 'status' => 'shipped']) . "\n\n";



Код:
eventSource.addEventListener('order_update', (event) => {
  const order = JSON.parse(event.data);
  updateOrderUI(order);
});


SSE Trade-offs​


Pros:

  • Works over standard HTTP/1.1 and HTTP/2
  • Auto-reconnect built into the browser spec
  • No special server infrastructure needed
  • Plays nicely with existing load balancers and proxies
  • Simple to implement and debug

Cons:

  • Unidirectional only — client communication needs separate AJAX/fetch calls
  • Limited to ~6 concurrent connections per domain in HTTP/1.1 (non-issue with HTTP/2)
  • Not ideal for very high-frequency updates (sub-100ms intervals)

The Decision Framework​


Here's a practical decision tree:


Код:
Do you need the CLIENT to send data through the real-time channel?
├── YES → WebSocket
└── NO  → Does the update frequency exceed ~10/second?
           ├── YES → WebSocket
           └── NO  → SSE (simpler, cheaper to run)


Use CaseRecommended
Live chat / messagingWebSocket
Multiplayer game stateWebSocket
Collaborative document editingWebSocket
Order status updatesSSE
Notification feedsSSE
Build/deployment log streamingSSE
Live sports scoresSSE
Real-time analytics dashboardDepends on frequency

A Note on Livewire and the TALL Stack​


If you're building with the TALL stack, Livewire 3 introduces wire:poll and the #[On] attribute with Laravel Echo, which abstracts much of this complexity. For many admin dashboards and notification panels, Livewire's built-in polling or event broadcasting handles real-time needs without writing WebSocket or SSE code manually.

That said, understanding the underlying protocols matters when you need to optimize performance or handle edge cases — something the team at HanzWeb encounters regularly when building data-intensive dashboards for clients across industries.

Production Considerations​


For WebSocket:

  • Use Laravel Reverb with Redis as the pub/sub backend for horizontal scaling
  • Configure proper SSL termination at the load balancer level
  • Implement heartbeat/ping-pong to detect dead connections

For SSE:

  • Set fastcgi_buffering off in your Nginx config (or use X-Accel-Buffering: no)
  • For long-running PHP processes, ensure set_time_limit(0) and monitor memory usage
  • Consider using a queue worker to push events to a Redis channel, then have the SSE endpoint subscribe — this avoids database polling in the stream loop

Conclusion​


WebSocket and SSE aren't competing technologies — they're complementary tools for different shapes of problems. SSE is dramatically underused; it covers a large portion of real-time use cases with far less operational complexity than a full WebSocket setup. If your server just needs to tell the client something happened, SSE is often the cleaner, more pragmatic choice.

Reach for WebSocket when you genuinely need bidirectionality or sub-second, high-frequency updates. Both are well-supported, production-proven, and worth having in your toolkit.

 
Назад
Сверху Снизу