Server-Sent Events and JavaScript EventSource

Server-Sent Events (SSE) enable a web server to push real-time data updates to a client over a standard, long-lived HTTP connection. This article explains the fundamentals of Server-Sent Events, how the underlying communication protocol works, and how to use the native JavaScript EventSource API to establish a connection, handle incoming data streams, manage custom event types, and handle connection errors.

What Are Server-Sent Events?

Server-Sent Events (SSE) is a standardized web technology defined under HTML5 that allows servers to stream real-time text data to web browsers asynchronously. Unlike traditional polling, where the client repeatedly asks the server for new information, SSE establishes a persistent connection where the server pushes updates automatically whenever new data is available.

SSE provides unidirectional (one-way) communication from the server to the client. This makes it a lightweight alternative to WebSockets for use cases that only require incoming updates, such as live sports scores, stock tickers, system monitoring dashboards, news feeds, and social media notifications.

How Server-Sent Events Work

SSE operates over standard HTTP or HTTP/2 transport. To start an SSE stream, the server responds to an incoming HTTP GET request with specific headers:

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

The payload is sent as plain text formatted into fields separated by newlines. A double newline (\n\n) marks the end of a single event message.

A typical stream message can contain the following fields:

Example Server Payload

id: 1
event: priceUpdate
data: {"symbol": "AAPL", "price": 185.50}

id: 2
data: This is a standard unnamed message.

Consuming SSE in JavaScript with EventSource

Browsers provide a built-in EventSource interface that manages the SSE connection lifecycle, including automatic reconnections and event parsing.

1. Opening a Connection

To open an SSE connection, instantiate the EventSource class with the target server URL:

const eventSource = new EventSource('/api/stream');

If your endpoint requires authentication cookies or credentials across domains, pass a configuration object:

const eventSource = new EventSource('https://api.example.com/stream', {
  withCredentials: true
});

2. Listening for Messages

To handle default messages (messages sent without a specific event: field), use the onmessage event handler:

eventSource.onmessage = (event) => {
  console.log('New message received:', event.data);
  
  // Parse JSON if the server sends serialized data
  try {
    const parsedData = JSON.parse(event.data);
    console.log('Parsed data:', parsedData);
  } catch (err) {
    console.log('Raw text:', event.data);
  }
};

3. Listening for Named Custom Events

When the server specifies an event: name, you must attach an event listener matching that specific event name:

eventSource.addEventListener('priceUpdate', (event) => {
  const stock = JSON.parse(event.data);
  console.log(`Stock: ${stock.symbol}, New Price: ${stock.price}`);
});

4. Handling Connection Lifecycle and Errors

EventSource provides onopen and onerror handlers to monitor connection status:

eventSource.onopen = () => {
  console.log('SSE connection successfully established.');
};

eventSource.onerror = (error) => {
  if (eventSource.readyState === EventSource.CONNECTING) {
    console.log('Connection lost. Attempting to reconnect...');
  } else if (eventSource.readyState === EventSource.CLOSED) {
    console.error('Connection closed permanently.', error);
  }
};

5. Closing the Connection

When data is no longer needed, close the connection to free up server and network resources:

eventSource.close();
console.log('SSE connection closed by the client.');

Key Advantages of Using SSE