HTML Server-Sent Events API

Receive automatic updates from servers using the EventSource API. Build real-time applications with one-way data streaming from server to client.

Server-Sent Events (SSE) provide a simple way for servers to push data to web pages over HTTP. Unlike WebSockets which enable two-way communication, SSE offers a lightweight solution for one-directional updates perfect for live feeds, notifications, and continuous data streams.

What are Server-Sent Events?

Server-Sent Events create a persistent connection between your webpage and the server. Once established, the server can send messages whenever new data becomes available without the client requesting it. This approach works exceptionally well for applications displaying real-time information like stock prices, social media feeds, or live sports scores.

Key Benefits of SSE

  • Automatic reconnection when connection drops
  • Works over standard HTTP/HTTPS protocols
  • Simple text-based format easy to implement
  • Lower overhead compared to polling
  • Built-in event tracking with IDs
  • Native browser support without libraries

Creating Your First EventSource Connection

The EventSource interface handles all connection management automatically. You provide a URL endpoint, and the browser maintains the connection, processes incoming messages, and reconnects if needed.

Basic EventSource Example

<div id="updates"></div>

<script>
// Create connection to server endpoint
const eventSource = new EventSource('/api/stream');

// Listen for messages
eventSource.onmessage = function(event) {
  const updateDiv = document.getElementById('updates');
  const newMessage = document.createElement('p');
  newMessage.textContent = event.data;
  updateDiv.appendChild(newMessage);
};

// Handle connection opened
eventSource.onopen = function() {
  console.log('Connection established');
};

// Handle errors
eventSource.onerror = function(error) {
  console.error('Connection error:', error);
};
</script>

Server Response Format

Servers send data as plain text with specific formatting. Each message consists of fields separated by newlines, with a blank line marking the message boundary. The format remains straightforward and easy to generate from any backend language.

Server-Side Format

// HTTP Headers (required)
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

// Message format
data: This is a simple message

data: You can also send
data: multi-line messages
data: like this

id: 123
event: customEvent
data: {"user": "John", "action": "login"}

retry: 10000

Field Descriptions:

  • data: - The actual message content (required)
  • id: - Unique identifier for message tracking
  • event: - Custom event name (defaults to "message")
  • retry: - Reconnection time in milliseconds

Working with Custom Events

Beyond the default message event, you can define custom event types to organize different data streams. This approach helps separate concerns when handling multiple types of updates from the same connection.

Custom Event Handlers

const source = new EventSource('/api/notifications');

// Handle different event types
source.addEventListener('userJoined', function(e) {
  const data = JSON.parse(e.data);
  console.log(`${data.username} joined the chat`);
  updateUserList(data);
});

source.addEventListener('newMessage', function(e) {
  const message = JSON.parse(e.data);
  displayMessage(message);
});

source.addEventListener('userLeft', function(e) {
  const data = JSON.parse(e.data);
  console.log(`${data.username} left`);
  removeFromUserList(data.userId);
});

// Still handle generic messages
source.onmessage = function(e) {
  console.log('Generic message:', e.data);
};

Managing Connections

Proper connection management ensures your application remains responsive and doesn't waste resources. Always close connections when they're no longer needed, and handle errors gracefully to maintain a good user experience.

Connection Control

let eventSource = null;

function startConnection() {
  // Check if already connected
  if (eventSource && eventSource.readyState !== EventSource.CLOSED) {
    console.log('Already connected');
    return;
  }

  eventSource = new EventSource('/stream');

  eventSource.onopen = function() {
    document.getElementById('status').textContent = 'Connected';
    document.getElementById('status').className = 'status-online';
  };

  eventSource.onerror = function() {
    document.getElementById('status').textContent = 'Connection lost';
    document.getElementById('status').className = 'status-offline';
  };

  eventSource.onmessage = function(e) {
    processUpdate(e.data);
  };
}

function stopConnection() {
  if (eventSource) {
    eventSource.close();
    eventSource = null;
    document.getElementById('status').textContent = 'Disconnected';
    document.getElementById('status').className = 'status-offline';
  }
}

// Clean up when page unloads
window.addEventListener('beforeunload', stopConnection);

Connection States

The EventSource object maintains a readyState property indicating the current connection status. Understanding these states helps you build robust applications that respond appropriately to connection changes.

Checking Connection State

function checkConnectionState(source) {
  switch(source.readyState) {
    case EventSource.CONNECTING:
      console.log('Connecting to server...');
      return 'connecting';

    case EventSource.OPEN:
      console.log('Connection active');
      return 'open';

    case EventSource.CLOSED:
      console.log('Connection closed');
      return 'closed';

    default:
      console.log('Unknown state');
      return 'unknown';
  }
}

// Monitor connection
const monitor = new EventSource('/updates');
setInterval(function() {
  const state = checkConnectionState(monitor);
  updateStatusIndicator(state);
}, 5000);
State Value Description
CONNECTING 0 Connection being established
OPEN 1 Connection active and receiving events
CLOSED 2 Connection closed, won't reconnect

Real-World Example: Live Notifications

This complete example demonstrates a notification system that receives live updates from a server. The implementation includes connection status indicators, automatic reconnection, and proper cleanup.

Live Notification System

<!DOCTYPE html>
<html lang="en">
<head>
  <style>
    #notifications {
      max-width: 600px;
      margin: 20px auto;
    }
    .notification {
      padding: 15px;
      margin: 10px 0;
      border-radius: 5px;
      background: #f0f0f0;
      border-left: 4px solid #3498db;
    }
    .notification.error {
      border-left-color: #e74c3c;
      background: #fee;
    }
    .notification.success {
      border-left-color: #2ecc71;
      background: #efe;
    }
    #status {
      padding: 10px;
      text-align: center;
      font-weight: bold;
    }
    .status-online { color: #2ecc71; }
    .status-offline { color: #e74c3c; }
  </style>
</head>
<body>
  <div id="status" class="status-offline">Disconnected</div>
  <div id="notifications"></div>
  <button onclick="toggleConnection()">Toggle Connection</button>

  <script>
    let eventSource = null;
    let isConnected = false;

    function addNotification(message, type = 'info') {
      const container = document.getElementById('notifications');
      const notification = document.createElement('div');
      notification.className = `notification ${type}`;
      notification.innerHTML = `
        <small>${new Date().toLocaleTimeString()}</small>
        <p>${message}</p>
      `;
      container.insertBefore(notification, container.firstChild);

      // Keep only last 10 notifications
      while (container.children.length > 10) {
        container.removeChild(container.lastChild);
      }
    }

    function connect() {
      eventSource = new EventSource('/api/notifications');

      eventSource.onopen = function() {
        document.getElementById('status').textContent = 'Connected';
        document.getElementById('status').className = 'status-online';
        isConnected = true;
      };

      eventSource.addEventListener('notification', function(e) {
        const data = JSON.parse(e.data);
        addNotification(data.message, data.type);
      });

      eventSource.onerror = function() {
        document.getElementById('status').textContent = 'Connection Error';
        document.getElementById('status').className = 'status-offline';
      };
    }

    function disconnect() {
      if (eventSource) {
        eventSource.close();
        isConnected = false;
        document.getElementById('status').textContent = 'Disconnected';
        document.getElementById('status').className = 'status-offline';
      }
    }

    function toggleConnection() {
      if (isConnected) {
        disconnect();
      } else {
        connect();
      }
    }

    // Auto-connect on page load
    connect();
  </script>
</body>
</html>

Browser Compatibility

Server-Sent Events work in all modern browsers. Internet Explorer doesn't support SSE, but polyfills exist for older browsers if needed. For production applications, always check compatibility and provide fallbacks when necessary.

Feature Detection

// Check for SSE support
if (typeof EventSource !== 'undefined') {
  // SSE is supported
  const source = new EventSource('/stream');
  // ... handle events
} else {
  // Fall back to polling or WebSockets
  console.warn('SSE not supported, using fallback');
  startPolling();
}

function startPolling() {
  setInterval(function() {
    fetch('/api/updates')
      .then(response => response.json())
      .then(data => processUpdates(data))
      .catch(error => console.error('Polling error:', error));
  }, 5000);
}

Best Practices

Server Implementation

  • Set proper HTTP headers (Content-Type: text/event-stream)
  • Disable caching with Cache-Control: no-cache
  • Keep connections alive with periodic heartbeat messages
  • Include message IDs for client-side tracking
  • Implement connection limits per user to prevent abuse

Client Implementation

  • Always close connections when leaving the page
  • Handle errors gracefully with user feedback
  • Limit the number of simultaneous connections
  • Parse JSON data safely with try-catch blocks
  • Monitor memory usage when displaying large data streams

Common Pitfalls

  • Forgetting to close connections causes memory leaks
  • Not handling errors leads to poor user experience
  • Missing CORS headers prevents cross-origin connections
  • Blocking server processes while waiting for data
  • Sending binary data (SSE only supports text)

SSE vs WebSockets

Choosing between Server-Sent Events and WebSockets depends on your specific needs. SSE excels in scenarios where the server primarily sends data to clients, while WebSockets suit applications requiring bidirectional communication.

Feature Server-Sent Events WebSockets
Communication One-way (server to client) Two-way (bidirectional)
Protocol HTTP/HTTPS WS/WSS
Data Format Text only (UTF-8) Text and binary
Auto Reconnect Built-in Manual implementation
Complexity Simple More complex
Use Cases News feeds, notifications, dashboards Chat, gaming, collaborative tools

Common Use Cases

Stock Price Updates

Financial applications use SSE to stream real-time price changes to thousands of connected clients simultaneously. The automatic reconnection feature ensures users stay updated even with network interruptions.

Social Media Feeds

Social platforms deliver new posts and notifications as they occur without requiring users to refresh pages. This creates an engaging, responsive experience while minimizing server load compared to constant polling.

Server Monitoring Dashboards

System administrators watch live server metrics and logs through SSE connections. The persistent connection provides instant alerts about issues while keeping implementation simple and resource-efficient.

Live Sports Scores

Sports websites broadcast score updates to viewers in real-time. SSE handles variable traffic loads well, scaling easily during peak events while automatically managing connections.

Try Our HTML Playground

Click the button below to open our interactive HTML editor. Write HTML code and see the results immediately!

Open HTML Playground ›