Building Real-Time Features with WebSockets and Server-Sent Events | SoniNow Blog

Limited TimeLearn More

websocketserver-sent eventsreal-timeapiweb development

Building Real-Time Features with WebSockets and Server-Sent Events

Published

2026-06-23

Read Time

4 mins

Building Real-Time Features with WebSockets and Server-Sent Events

Real-time features are no longer optional — users expect live updates, instant notifications, and collaborative experiences. WebSockets and Server-Sent Events (SSE) are the two primary technologies for building them. Each has strengths and trade-offs that determine the right use case.

WebSockets: Full-Duplex Communication

WebSockets maintain a persistent TCP connection between client and server, allowing bidirectional data flow. Use them when the client needs to send data to the server as frequently as it receives updates — chat applications, collaborative editing, and multiplayer games.

// Server-side WebSocket with Node.js
import { WebSocketServer } from 'ws'

const wss = new WebSocketServer({ port: 8080 })

wss.on('connection', (ws, req) => {
  const userId = authenticateConnection(req.url)
  if (!userId) {
    ws.close(4001, 'Unauthorized')
    return
  }

  ws.on('message', (data) => {
    const message = JSON.parse(data.toString())
    broadcastToRoom(message.roomId, {
      userId,
      content: message.content,
      timestamp: Date.now(),
    })
  })

  ws.on('close', () => {
    handleDisconnect(userId)
  })
})
// Client-side WebSocket
const ws = new WebSocket('wss://api.example.com/ws?token=jwt-token')

ws.onmessage = (event) => {
  const update = JSON.parse(event.data)
  // Update UI with the new data
  addMessageToChat(update)
}

ws.send(JSON.stringify({ type: 'message', roomId: 'general', content: 'Hello' }))

Connection management is critical with WebSockets. Implement heartbeat ping/pong intervals to detect stale connections, and reconnection logic with exponential backoff on the client side.

Server-Sent Events: Simpler Server-to-Client Streaming

SSE opens a single HTTP connection from the client to the server, and the server pushes events over that connection. It is unidirectional — the client cannot send data over the same connection. SSE is significantly simpler to implement than WebSockets and works over HTTP/1.1 and HTTP/2.

// Next.js Route Handler using SSE
export async function GET(request: NextRequest) {
  const stream = new ReadableStream({
    start(controller) {
      const encoder = new TextEncoder()

      // Send initial connection event
      controller.enqueue(
        encoder.encode(`data: ${JSON.stringify({ type: 'connected' })}\n\n`)
      )

      // Push updates every 5 seconds
      const interval = setInterval(async () => {
        const updates = await fetchNewUpdates()
        if (updates.length > 0) {
          for (const update of updates) {
            controller.enqueue(
              encoder.encode(`data: ${JSON.stringify(update)}\n\n`)
            )
          }
        }
      }, 5000)

      // Cleanup on disconnect
      request.signal.addEventListener('abort', () => {
        clearInterval(interval)
        controller.close()
      })
    },
  })

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
    },
  })
}
// Client-side SSE
const eventSource = new EventSource('/api/updates')

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data)
  updateDashboard(data)
}

eventSource.addEventListener('notification', (event) => {
  const notification = JSON.parse(event.data)
  showToast(notification)
})

SSE automatically reconnects when the connection drops — no manual reconnection logic needed. It is the right choice for live dashboards, stock tickers, notification feeds, and activity streams.

Choosing Between WebSockets and SSE

Use Case                     | WebSocket  | SSE
-----------------------------|------------|----------------
Chat / messaging             | ✅ Best    | ❌ No client send
Live dashboard               | Possible   | ✅ Best
Real-time notifications      | Overkill   | ✅ Best
Collaborative editing        | ✅ Best    | ❌ No client send
File upload progress         | Possible   | ✅ Best
Gaming / low-latency         | ✅ Best    | ❌ Too much overhead
Stock ticker / price feeds   | Possible   | ✅ Best

The decision comes down to whether your client needs to send data. If it does not, SSE is almost always the simpler, more reliable choice. If bidirectional communication is required, WebSockets are the answer.

Scaling Real-Time Connections

Both technologies face scaling challenges. A single server handles only so many concurrent connections. For production deployments:

  • Use a message broker (Redis Pub/Sub, RabbitMQ) to broadcast events across multiple server instances
  • Front connections with a load balancer that supports sticky sessions or route via a WebSocket-aware proxy
  • Consider managed services — Pusher for WebSockets, or serverless SSE with Cloudflare Durable Objects
// Scaling with Redis Pub/Sub
import { createClient } from 'redis'

const pub = createClient()
const sub = createClient()

sub.subscribe('notifications', (message) => {
  // Broadcast to all connected WebSocket clients on this server
  wss.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message)
    }
  })
})

Each server instance subscribes to the same channels. Any server that publishes an event sends it through Redis, and every other server receives and broadcasts it to its local connections.

Building real-time features requires careful architecture decisions around protocol choice, connection management, and horizontal scaling. At SoniNow, we design and implement real-time systems that handle thousands of concurrent connections with reliable delivery and low latency.

Ready to add real-time features to your application? Talk to SoniNow about your use case and let us architect the right solution.