SalakCode SalakCode
Networking

Server-Sent Events

One-way real-time server-to-client streaming

Intermediate
http real-time streaming push

Definition

Server-Sent Events (SSE) is a server push technology enabling a browser to receive automatic updates from a server via HTTP connection. Unlike WebSockets, SSE is unidirectional (server-to-client only), simpler to implement, automatically reconnects, and works over standard HTTP.

SSE vs WebSockets

FeatureSSEWebSockets
DirectionServer → Client onlyBidirectional
ProtocolHTTPWebSocket (ws://)
Auto-reconnectYesNo (manual)
Binary dataNo (text only)Yes
Browser supportGood (IE11 needs polyfill)Excellent
ComplexitySimpleMore complex
Use caseUpdates, notificationsChat, gaming

Basic Implementation

Client-Side

// Create EventSource
const source = new EventSource('/api/events');

// Listen for messages
source.onmessage = (event) => {
  console.log('New message:', event.data);
  const data = JSON.parse(event.data);
  updateUI(data);
};

// Listen for specific event types
source.addEventListener('price-update', (event) => {
  const price = JSON.parse(event.data);
  updatePrice(price);
});

source.addEventListener('notification', (event) => {
  showNotification(JSON.parse(event.data));
});

// Handle connection open
source.onopen = () => {
  console.log('Connection opened');
};

// Handle errors
source.onerror = (error) => {
  console.error('EventSource error:', error);
  // Auto-reconnects automatically!
};

// Close connection
function closeConnection() {
  source.close();
}

Server-Side (Node.js)

const http = require('http');

const server = http.createServer((req, res) => {
  if (req.url === '/api/events') {
    // SSE headers
    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
      'Access-Control-Allow-Origin': '*'
    });
    
    // Send initial connection established
    res.write('data: {"type": "connected"}\n\n');
    
    // Send periodic updates
    const interval = setInterval(() => {
      const data = {
        timestamp: new Date().toISOString(),
        price: Math.random() * 100
      };
      
      res.write(`data: ${JSON.stringify(data)}\n\n`);
    }, 1000);
    
    // Clean up on disconnect
    req.on('close', () => {
      clearInterval(interval);
    });
  }
});

server.listen(3000);

SSE Protocol Format

Format:
id: message-id\n        <- Optional: ID for tracking
event: custom-type\n   <- Optional: Event type name
data: message payload\n <- Required: Data (can be multiple lines)
retry: 5000\n          <- Optional: Reconnection time (ms)
\n                     <- Empty line = send message

Example:
id: 123
event: price-update
data: {"symbol": "AAPL", "price": 150.25}

id: 124
event: price-update  
data: {"symbol": "GOOGL", "price": 2750.00}

: heartbeat comment\n  <- Comment (ignored, useful for keep-alive)

Advanced Usage

With Express

const express = require('express');
const app = express();

app.get('/events', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  
  // Send custom event
  res.write('event: user-login\n');
  res.write('data: {"user": "john"}\n\n');
  
  // Regular message
  res.write('data: Hello World\n\n');
});

React Hook

function useServerSentEvents(url) {
  const [data, setData] = useState(null);
  const [connected, setConnected] = useState(false);
  
  useEffect(() => {
    const source = new EventSource(url);
    
    source.onopen = () => setConnected(true);
    source.onmessage = (event) => {
      setData(JSON.parse(event.data));
    };
    source.onerror = () => setConnected(false);
    
    return () => source.close();
  }, [url]);
  
  return { data, connected };
}

// Usage
function StockTicker() {
  const { data, connected } = useServerSentEvents('/api/stocks');
  
  return (
    <div>
      <div>{connected ? '🟢' : '🔴'}</div>
      {data && <div>{data.symbol}: ${data.price}</div>}
    </div>
  );
}

Custom Event Types

// Server sends different event types
res.write('event: stock-update\n');
res.write('data: {"symbol": "AAPL", "price": 150}\n\n');

res.write('event: notification\n');
res.write('data: {"message": "Order completed"}\n\n');

res.write('event: error\n');
res.write('data: {"message": "Connection limit reached"}\n\n');

// Client handles each type
source.addEventListener('stock-update', handleStockUpdate);
source.addEventListener('notification', handleNotification);
source.addEventListener('error', handleError);

Use Cases

1. Live Updates

// Stock prices, sports scores, news feeds
// Real-time dashboards
// System monitoring

2. Notifications

// Social media notifications
// Email alerts
// System alerts

3. Progress Tracking

// File upload progress
// Build/deployment status
// Long-running task updates

Best Practices

1. Connection Management

// Limit concurrent connections
// Close idle connections
// Handle reconnections gracefully

2. Error Handling

source.onerror = (error) => {
  // Don't panic - browser auto-reconnects
  console.log('Connection lost, retrying...');
};

// Set custom retry time (server-side)
res.write('retry: 10000\n\n'); // Wait 10s before reconnect

3. Security

// Use withSession or authentication
app.get('/events', authenticate, (req, res) => {
  // Only send events user is authorized for
  const userId = req.user.id;
  
  res.setHeader('Content-Type', 'text/event-stream');
  
  // Send user-specific updates
  userUpdates.on(userId, (update) => {
    res.write(`data: ${JSON.stringify(update)}\n\n`);
  });
});
Key Takeaway

SSE provides simple, automatic server-to-client streaming over HTTP. Use it for live updates, notifications, and progress tracking where you only need server pushes. It auto-reconnects, works with standard HTTP infrastructure, and is easier to implement than WebSockets for unidirectional use cases.

Resources

Related Topics