Skip to content

HTML5 - Server-Sent Events

Server-Sent Events (SSE) is a standard enabling web servers to push data updates to web clients (browsers) over a single, long-lived HTTP connection. Unlike WebSockets (which are bidirectional), SSE is unidirectional: data flows only from the server to the client.

This makes SSE a simpler alternative to WebSockets when you only need the server to send updates to the client, such as live news feeds, stock tickers, status updates, notifications, or monitoring dashboards.

SSE uses a standard HTTP connection and a specific text-based event stream format, making it potentially easier to implement on some server infrastructures compared to WebSockets and more resilient across proxies that might interfere with WebSocket connections.

On the client-side, you use the EventSource interface to connect to an SSE endpoint.

// Create an EventSource instance, connecting to the server endpoint
const source = new EventSource('/sse-endpoint'); // Replace with your actual endpoint URL

The browser will automatically attempt to reconnect if the connection is lost.

You listen for messages using event listeners attached to the EventSource object:

  • onopen: Fired when the connection to the server is successfully established.
  • onmessage: Fired when a message without a specific event name is received from the server. The event object’s data property contains the message payload.
  • onerror: Fired if an error occurs (e.g., connection failure). Note: This doesn’t fire for HTTP error status codes like 404 or 500 initially, only if the connection breaks.
  • Custom Events: If the server sends events with specific names (using the event: field), you listen for them using addEventListener('eventName', handler).
<!DOCTYPE html>
<html>
<head>
<title>Server-Sent Events Example</title>
</head>
<body>
<h1>Server Time Updates (SSE)</h1>
<div id="timeDisplay">Waiting for server time...</div>
<div id="status">Connecting...</div>
<script>
const timeDisplay = document.getElementById('timeDisplay');
const statusDiv = document.getElementById('status');
// Check for browser support
if (typeof(EventSource) !== "undefined") {
// Replace '/get-time-sse' with your actual server endpoint
const source = new EventSource('/get-time-sse');
statusDiv.textContent = 'Connecting to event source...';
// Called when the connection is opened
source.onopen = function(event) {
statusDiv.textContent = 'Connection opened!';
console.log("SSE Connection opened.", event);
};
// Called for messages with no specific event name (default)
source.onmessage = function(event) {
// Assuming the server sends time as plain text in the data field
timeDisplay.textContent = `Server time: ${event.data}`;
console.log("SSE Message received:", event.data);
};
// Example: Listening for a custom named event 'userUpdate'
source.addEventListener('userUpdate', function(event) {
console.log("Custom 'userUpdate' event received:", event.data);
// Process user update data (e.g., parse JSON)
// const userData = JSON.parse(event.data);
// updateUI(userData);
}, false);
// Called when an error occurs (e.g., connection lost)
source.onerror = function(event) {
statusDiv.textContent = 'Connection error. Reconnecting...';
console.error("SSE Error occurred:", event);
// The browser will automatically attempt to reconnect.
// If the server explicitly closes the connection (e.g., HTTP 204),
// or if readyState becomes CLOSED, you might want to stop reconnecting:
if (source.readyState === EventSource.CLOSED) {
statusDiv.textContent = 'Connection closed permanently by server.';
source.close(); // Ensure it's closed client-side too
}
};
} else {
statusDiv.textContent = 'Sorry, your browser does not support Server-Sent Events.';
}
</script>
</body>
</html>

You can explicitly close the connection from the client-side using the close() method:

// Close the connection
source.close();
console.log("SSE connection closed by client.");

The server-side script needs to:

  • Set the Content-Type header to text/event-stream.
  • Set appropriate caching headers (e.g., Cache-Control: no-cache) to prevent intermediate proxies from buffering the response.
  • Keep the connection open.
  • Send data formatted according to the SSE specification.

Messages are plain text, separated by two newline characters (\n\n). Each message can consist of one or more fields, each ending with a single newline (\n). Common fields are:

  • data: <message content>: The actual data payload for the event. Can span multiple lines if each starts with data: .
  • event: <event name>: Specifies a name for the event. If omitted, the message triggers the onmessage handler on the client.
  • id: <unique id>: Sets a unique ID for the event. If the connection drops, the browser will send a Last-Event-ID HTTP header with the last received ID, allowing the server to resume the stream.
  • retry: <milliseconds>: Specifies how long the client should wait before attempting to reconnect if the connection is lost (in milliseconds).

This basic Node.js example sends the current server time every second.

// server.js (Node.js example)
const http = require('http');
const server = http.createServer((req, res) => {
// Route for the SSE endpoint
if (req.url === '/get-time-sse' && req.method === 'GET') {
// Set headers for SSE
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive', // Important to keep connection open
'Access-Control-Allow-Origin': '*' // Optional: Add CORS header if needed
});
console.log('Client connected to SSE endpoint.');
// Send an initial message immediately (optional)
res.write(`data: Server connected at ${new Date().toLocaleTimeString()}\n\n`);
// Send time updates every second
const intervalId = setInterval(() => {
const currentTime = new Date().toLocaleTimeString();
// Simple message (triggers 'onmessage')
res.write(`data: ${currentTime}\n\n`);
// Example of sending a named event and an ID
// res.write(`id: ${Date.now()}\n`);
// res.write(`event: serverTimeUpdate\n`);
// res.write(`data: ${JSON.stringify({ time: currentTime })}\n\n`);
}, 1000);
// Handle client disconnect
req.on('close', () => {
console.log('Client disconnected from SSE endpoint.');
clearInterval(intervalId); // Stop sending updates
res.end(); // End the response
});
} else {
// Handle other requests (e.g., serve the HTML page)
res.writeHead(404);
res.end('Not Found');
}
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});

To run this, save it as server.js and run node server.js. Then, access the HTML page (you’d need to serve that separately or add another route) and update its EventSource URL to http://localhost:3000/get-time-sse.

  • Directionality: SSE is server-to-client only; WebSockets are bidirectional.
  • Protocol: SSE uses standard HTTP(S); WebSockets use a different protocol (ws/wss) after an initial HTTP handshake.
  • Complexity: SSE is generally considered simpler to implement, especially on the client-side.
  • Overhead: SSE typically has less protocol overhead per message than WebSockets.
  • Proxy/Firewall Compatibility: SSE often works better through restrictive proxies that might block WebSocket connections.
  • Built-in Features: SSE has built-in support for automatic reconnection and event IDs, which need manual implementation with WebSockets.

Choose SSE when you primarily need to push updates from the server to the client. Choose WebSockets for full real-time, two-way communication.