Skip to content

Sse Event Source Api

DodaTech 5 min read

title: "EventSource API in Browsers" description: "Learn how to use the EventSource API in browsers for consuming Server-Sent Events, handling connection states, and implementing advanced patterns." weight: 14 date: 2026-06-28 lastmod: 2026-06-28 tags: ["apis", "sse"]


The EventSource API is the browser-side interface for consuming SSE streams. It provides automatic reconnection, event dispatching, and connection state management with minimal code.

## What You'll Learn

- EventSource constructor and options
- Event handling: onmessage, onerror, onopen
- Named event listeners
- Connection state tracking
- Closing and cleanup

## Why It Matters

The EventSource API handles all the complexity of SSE consumption automatically. Understanding its API ensures you use it correctly and handle edge cases.

## Real-World Use

A live analytics dashboard creates an EventSource for each metric category. CPU metrics use named events, memory metrics use onmessage, and a connection status indicator tracks the EventSource readyState.

## Flow Chart

```mermaid
stateDiagram-v2
    [*] --> CONNECTING: new EventSource()
    CONNECTING --> OPEN: Connection established
    CONNECTING --> CLOSED: Failed permanently
    OPEN --> CLOSED: close() called
    OPEN --> CONNECTING: Connection lost

Code Examples

Example 1: Complete EventSource Usage

// Create EventSource with options
const source = new EventSource('/api/events', {
  withCredentials: true, // Include cookies for CORS
});

// Connection opened
source.onopen = (event) => {
  console.log('SSE connection established');
  updateConnectionStatus('connected');
};

// Unnamed events
source.onmessage = (event) => {
  console.log('Received:', event.data);
  console.log('Last event ID:', event.lastEventId);
  updateDashboard(event.data);
};

// Connection errors
source.onerror = (event) => {
  if (source.readyState === EventSource.CLOSED) {
    console.error('SSE connection failed permanently');
    updateConnectionStatus('disconnected');
    showReconnectButton();
  } else if (source.readyState === EventSource.CONNECTING) {
    console.log('SSE reconnecting...');
    updateConnectionStatus('reconnecting');
  }
};

// Named events
source.addEventListener('stock-update', (event) => {
  const data = JSON.parse(event.data);
  updateStockPrice(data.symbol, data.price);
});

source.addEventListener('news-alert', (event) => {
  showNotification(event.data);
});

source.addEventListener('system-status', (event) => {
  updateSystemStatus(JSON.parse(event.data));
});

// Check connection state
function getConnectionStatus() {
  switch (source.readyState) {
    case 0: return 'CONNECTING';
    case 1: return 'OPEN';
    case 2: return 'CLOSED';
  }
}

// Clean up
function cleanup() {
  source.close();
  console.log('SSE connection closed');
}

Expected output: EventSource connects, dispatches events to appropriate handlers, tracks connection state, and handles errors gracefully.

Example 2: Multiple EventSources

class SSEManager {
  constructor() {
    this.sources = new Map();
    this.listeners = new Map();
  }

  connect(name, url, options = {}) {
    // Close existing connection
    if (this.sources.has(name)) {
      this.disconnect(name);
    }

    const source = new EventSource(url, options);
    this.sources.set(name, source);

    source.onopen = () => {
      console.log(`SSE '${name}' connected`);
      this.emit('connection', { name, status: 'connected' });
    };

    source.onerror = (event) => {
      if (source.readyState === EventSource.CLOSED) {
        console.error(`SSE '${name}' failed`);
        this.emit('connection', { name, status: 'failed' });
      }
    };

    source.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);
        this.emit('message', { name, data });
      } catch (e) {
        console.warn('Parse error for', name, event.data);
      }
    };

    return source;
  }

  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event).add(callback);
    return () => this.listeners.get(event)?.delete(callback);
  }

  emit(event, data) {
    this.listeners.get(event)?.forEach(cb => cb(data));
  }

  disconnect(name) {
    const source = this.sources.get(name);
    if (source) {
      source.close();
      this.sources.delete(name);
      this.emit('connection', { name, status: 'disconnected' });
    }
  }

  disconnectAll() {
    this.sources.forEach((source, name) => this.disconnect(name));
  }
}

// Usage
const sseManager = new SSEManager();

sseManager.connect('stocks', '/api/stocks');
sseManager.connect('news', '/api/news', { withCredentials: true });

sseManager.on('message', ({ name, data }) => {
  console.log(`[${name}]`, data);
});

sseManager.on('connection', ({ name, status }) => {
  updateIndicator(name, status);
});

// Cleanup on page unload
window.addEventListener('beforeunload', () => {
  sseManager.disconnectAll();
});

Expected output: Manager handles multiple concurrent EventSource connections with centralized event dispatching and lifecycle management.

Example 3: EventSource Polyfill for Older Browsers

// Check for EventSource support
if (typeof EventSource === 'undefined') {
  console.warn('EventSource not supported, loading polyfill');
  
  // Dynamic import of polyfill
  const script = document.createElement('script');
  script.src = 'https://cdn.jsdelivr.net/npm/event-source-polyfill@1.0.31/src/eventsource.min.js';
  document.head.appendChild(script);

  script.onload = () => {
    console.log('EventSource polyfill loaded');
    initApp();
  };
} else {
  initApp();
}

function initApp() {
  const source = new EventSource('/api/events');

  source.onmessage = (event) => {
    // Application code
  };
}

// Or use a custom fetch-based fallback
function createFallbackSSE(url) {
  let reader = null;
  let buffer = '';

  async function connect() {
    try {
      const response = await fetch(url, {
        headers: { 'Accept': 'text/event-stream' },
      });

      reader = response.body.getReader();
      const decoder = new TextDecoder();

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const events = buffer.split('\n\n');
        buffer = events.pop() || '';

        events.forEach(eventStr => {
          if (eventStr.trim()) {
            const event = parseSSEEvent(eventStr);
            if (event) {
              dispatchEvent(event);
            }
          }
        });
      }
    } catch (error) {
      console.error('SSE fallback error:', error);
      setTimeout(connect, 3000);
    }
  }

  connect();
}

Expected output: Polyfill or fallback implementation enables SSE support in older browsers that do not support EventSource natively.

Common Mistakes

Mistake Explanation
Forgetting to handle onerror Without error handling, silent failures leave the UI showing stale data
Not closing EventSource on page unload Open connections prevent page from unloading quickly and waste resources
Using innerHTML with SSE data Always sanitize or use textContent to prevent XSS from server data
Ignoring CORS withCredentials Cross-origin SSE requires both server headers and withCredentials option
Creating too many EventSource connections Each EventSource uses a separate HTTP connection; reuse or multiplex when possible

Practice Questions

  1. How do you create an EventSource with credentials?
  2. What are the three readyState values of EventSource?
  3. How do you listen for named events in EventSource?
  4. What happens when EventSource encounters a network error?
  5. How do you cleanly close an EventSource connection?

Challenge

Build a React component that uses EventSource to receive live updates. Include connection status indicator, automatic reconnection with backoff, named event handling, and proper cleanup on component unmount.

FAQ

Can EventSource send credentials?

Yes, set withCredentials: true in the constructor to send cookies and HTTP auth headers with the SSE request.

What is the default reconnection time for EventSource?

The default is 3 seconds. Override with the retry: field in the server response.

How many simultaneous EventSource connections can a browser handle?

Browsers limit connections per domain (typically 6-8). Each EventSource uses one connection.

Can EventSource connect to a different origin?

Yes, but the server must send CORS headers (Access-Control-Allow-Origin).

What happens to the lastEventId during reconnection?

The browser automatically sets the Last-Event-ID HTTP header on reconnection with the last received event ID.

Does EventSource work with HTTP redirects?

EventSource follows HTTP redirects (301, 302, 307). It will connect to the redirected URL.

Mini Project

Build an SSE client library that wraps EventSource with features: automatic reconnection with exponential backoff, event filtering, connection status events, and a simple API for adding/removing event listeners. Include TypeScript type definitions.

What's Next

Learn how to implement SSE with Express.js

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro