🚀AdvancedReal-Time Communication

Server-Sent Events (EventSource)

Receive real-time server-to-client push updates using the EventSource API simpler than WebSockets for one-way data streams.

Overview

Server-Sent Events (SSE) is a browser API for receiving real-time push notifications from a server over a persistent HTTP connection. Unlike WebSockets (bidirectional), SSE is unidirectional the server pushes data to the client. This simplicity makes it ideal for live feeds, dashboards, notifications, and progress updates. The browser opens an HTTP connection to a URL that returns `text/event-stream`. The server keeps the connection open and sends events as plain text. If the connection drops, the browser automatically reconnects no manual retry logic needed. The `EventSource` interface provides `onmessage`, `onerror`, `onopen`, and `addEventListener` for custom event types. SSE works over HTTP/1.1 and HTTP/2, supports CORS, and is much simpler to implement than WebSockets for read-only real-time data.

Key Points

  • •`new EventSource(url)` opens a persistent GET connection to a `text/event-stream` endpoint
  • •Server sends events formatted as `data: <message>\n\n` (double newline ends each event)
  • •`source.onmessage` fires for generic messages; `source.addEventListener("type", handler)` for named events
  • •Automatic reconnection: browser reconnects if connection drops (uses `retry:` to set reconnect delay)
  • •`id:` field in server messages sets the `source.lastEventId` used in `Last-Event-ID` header on reconnect
  • •`source.close()` closes the connection; `source.readyState`: 0=CONNECTING, 1=OPEN, 2=CLOSED
  • •Supports CORS with `new EventSource(url, { withCredentials: true })` for cross-origin with cookies

Examples

Client-side EventSource connection

<div id="notifications">
  <h2>Live Updates</h2>
  <ul id="events-list"></ul>
</div>

<button id="stop-btn">Stop Updates</button>

<script>
const eventSource = new EventSource('/api/stream');
const list = document.getElementById('events-list');

// Generic messages (data: ...)
eventSource.onmessage = (event) => {
  const li = document.createElement('li');
  li.textContent = event.data;
  list.prepend(li);
};

// Named event type (event: notification\ndata: ...)
eventSource.addEventListener('notification', (event) => {
  const data = JSON.parse(event.data);
  showToast(data.message, data.type);
});

// Named event type for progress
eventSource.addEventListener('progress', (event) => {
  const { percent, label } = JSON.parse(event.data);
  updateProgressBar(percent, label);
});

eventSource.onerror = (error) => {
  if (eventSource.readyState === EventSource.CLOSED) {
    console.log('Connection closed');
  } else {
    console.error('SSE error browser will reconnect automatically');
  }
};

document.getElementById('stop-btn').addEventListener('click', () => {
  eventSource.close();
});
</script>

The browser automatically reconnects on error or connection drop no manual retry needed.

Server-side event stream format (Node.js example)

// Server: Node.js / Express endpoint
// Content-Type MUST be text/event-stream
// Cache-Control MUST be no-cache

app.get('/api/stream', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Access-Control-Allow-Origin': '*', // for CORS
  });

  // Send a comment to keep connection alive (every 15s)
  const keepAlive = setInterval(() => res.write(':keepalive\n\n'), 15000);

  // Send a generic message
  res.write('data: Hello from server\n\n');

  // Send a named event with JSON data
  res.write('event: notification\n');
  res.write('data: {"message":"New order received","type":"success"}\n\n');

  // Send message with ID (for reconnection)
  res.write('id: 42\n');
  res.write('data: Message with ID\n\n');

  // Set reconnection delay (milliseconds)
  res.write('retry: 5000\n\n');

  // Clean up when client disconnects
  req.on('close', () => {
    clearInterval(keepAlive);
    res.end();
  });
});

Each event ends with a blank line (\n\n). Fields within an event end with a single newline.

SSE with CORS and credentials

<!-- Cross-origin SSE with cookies/credentials -->
<script>
// For cross-origin streams that need cookies
const source = new EventSource('https://api.example.com/stream', {
  withCredentials: true
});

// The server must respond with:
// Access-Control-Allow-Origin: https://yoursite.com (not *)
// Access-Control-Allow-Credentials: true

source.addEventListener('data-update', async (event) => {
  const update = JSON.parse(event.data);
  document.getElementById('counter').textContent = update.count;
});

// Check connection state
console.log(source.readyState);
// EventSource.CONNECTING = 0
// EventSource.OPEN = 1
// EventSource.CLOSED = 2
</script>

Common Mistakes

  • Not sending the correct Content-Type header (`text/event-stream`) on the server browsers reject other content types
  • Forgetting the double newline `\n\n` at the end of each event a single newline just adds a field, two newlines terminate the event
  • Using SSE when you need bidirectional communication for two-way messaging, use WebSockets or WebRTC

Note

HTTP/2 multiplexing allows many SSE connections on a single TCP connection the old HTTP/1.1 6-connection limit is no longer a concern with modern infrastructure.

Exercises

Write HTML to solve each exercise. Use Preview to see it render in a live iframe, then mark as done.

Loading editor…

Completed: 0 / 5 exercises

Knowledge Check

Question 1 of 4

What is the key difference between EventSource (SSE) and WebSockets?