DEV Community

Cover image for Real-Time APIs: WebSockets vs Server-Sent Events
APIVerve
APIVerve

Posted on • Originally published at blog.apiverve.com

Real-Time APIs: WebSockets vs Server-Sent Events

REST is request-response. You ask, server answers, connection closes. Works fine for most things.

But chat messages can't wait for you to ask. Stock prices need to push the moment they change. Notifications should appear, not be polled for.

Two options: WebSockets and Server-Sent Events. Different tools, different trade-offs.

The Quick Answer

WebSockets: Two-way communication. Client and server can both send anytime. Use for chat, games, collaborative editing.

Server-Sent Events (SSE): One-way. Server pushes to client. Use for notifications, live feeds, dashboards.

If you only need server-to-client, use SSE. It's simpler.

WebSockets

Full duplex. Once connected, either side can send messages whenever.

// Client
const socket = new WebSocket('wss://example.com/socket');

socket.onopen = () => {
  socket.send(JSON.stringify({ type: 'subscribe', channel: 'chat' }));
};

socket.onmessage = (event) => {
  const message = JSON.parse(event.data);
  displayMessage(message);
};

// Send a message
function sendChat(text) {
  socket.send(JSON.stringify({ type: 'chat', text }));
}
Enter fullscreen mode Exit fullscreen mode

The connection stays open. Messages flow both directions.

Good for:

  • Chat applications
  • Multiplayer games
  • Collaborative tools (multiple users editing)
  • Anything where clients send frequent messages

Annoying parts:

  • You handle reconnection yourself
  • Some proxies/firewalls don't like long-lived connections
  • More complex server infrastructure

Server-Sent Events

One direction: server to client. Built on regular HTTP.

// Client
const events = new EventSource('/api/notifications');

events.onmessage = (event) => {
  const notification = JSON.parse(event.data);
  showNotification(notification);
};

// That's it. Browser handles reconnection automatically.
Enter fullscreen mode Exit fullscreen mode
// Server (Express)
app.get('/api/notifications', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  // Send data whenever you want
  const send = (data) => {
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  };

  send({ type: 'connected' });

  // Clean up when client disconnects
  req.on('close', () => {
    // Remove from active connections
  });
});
Enter fullscreen mode Exit fullscreen mode

Good for:

  • Live notifications
  • Activity feeds
  • Dashboards and metrics
  • Stock tickers
  • Any "subscribe and receive updates" pattern

Why it's nice:

  • Automatic reconnection built in
  • Works through most proxies
  • Just HTTP — nothing special to deploy
  • Simpler server code

Side by Side

Aspect WebSockets SSE
Direction Both ways Server to client
Protocol Custom (ws://) Regular HTTP
Reconnection Manual Automatic
Binary data Yes Text only
Complexity Higher Lower
Proxy support Sometimes problematic Usually fine

When Polling Is Actually Fine

Real-time tech has overhead. Sometimes polling is the right call:

  • Updates are infrequent (minutes apart)
  • You can't maintain persistent connections
  • Simplicity matters more than immediacy

Polling every 30 seconds for weather data? Totally reasonable. Don't overcomplicate it.

Combining SSE with REST

You don't have to pick one protocol for everything. Common pattern:

  • SSE for receiving updates (server → client)
  • REST for sending actions (client → server)
// Receive notifications via SSE
const events = new EventSource('/api/feed');
events.onmessage = handleUpdate;

// Send actions via REST
async function postComment(text) {
  await fetch('/api/comments', {
    method: 'POST',
    body: JSON.stringify({ text })
  });
  // Server broadcasts update to all SSE clients
}
Enter fullscreen mode Exit fullscreen mode

Simpler than WebSockets, covers most use cases.

Getting Real-Time Data

You need something to stream. Options:

  1. Your own data — Database changes, user actions, internal events
  2. External APIs — Pull from data sources, push to your clients

For the second one, you poll the API and broadcast to connected clients:

// Poll external API, push to clients
setInterval(async () => {
  const weather = await fetch('https://api.apiverve.com/v1/weatherforecast?city=NYC', {
    headers: { 'x-api-key': process.env.API_KEY }
  }).then(r => r.json());

  broadcastToClients({ type: 'weather', data: weather.data });
}, 60000); // Every minute
Enter fullscreen mode Exit fullscreen mode

You absorb the polling. Users get real-time feel.

Scaling

One server, simple. Multiple servers, slightly harder.

Problem: User connects to Server A, but the event happens on Server B.

Solution: Pub/sub. Redis is common:

// When event happens (any server)
redis.publish('notifications', JSON.stringify({ userId, data }));

// All servers subscribe
redis.subscribe('notifications');
redis.on('message', (channel, message) => {
  const { userId, data } = JSON.parse(message);
  sendToUser(userId, data); // If they're connected to this server
});
Enter fullscreen mode Exit fullscreen mode

Event goes to Redis, all servers hear it, the one with the connection delivers it.

Making the Choice

Start with SSE if:

  • Users primarily receive updates
  • You want simpler infrastructure
  • Automatic reconnection matters

Use WebSockets if:

  • High-frequency bidirectional messages
  • Binary data needed
  • Building games or collaborative editing

Stick with polling if:

  • Updates are infrequent
  • You're optimizing for simplicity
  • Real-time isn't actually required

Most "real-time" features are actually SSE use cases masquerading as WebSocket projects. Start simple, upgrade if you actually need bidirectional.

For data to stream, check the APIVerve marketplace — weather, currency, stocks. Pull from APIs, push to your users.


Originally published at APIVerve Blog

Top comments (0)