Back to Documentation
Real-time Streaming Guide
Learn how to implement real-time currency rate updates using Server-Sent Events, WebSockets, and smart polling strategies.
What You'll Learn
- • Implement Server-Sent Events for real-time updates
- • Build WebSocket connections for bi-directional communication
- • Create smart polling strategies with exponential backoff
- • Handle connection management and reconnection logic
- • Optimize performance and reduce server load
Real-time Strategies Comparison
Server-Sent Events
- ✅ Simple HTTP-based protocol
- ✅ Automatic reconnection
- ✅ Built-in browser support
- ❌ One-way communication only
- ❌ Limited by HTTP connection limits
WebSockets
- ✅ Bi-directional communication
- ✅ Low latency
- ✅ Full-duplex connection
- ❌ More complex to implement
- ❌ Requires connection management
Smart Polling
- ✅ Universal compatibility
- ✅ Simple to implement
- ✅ Easy error handling
- ❌ Higher latency
- ❌ More server requests
Implementation Guide
1. Server-Sent Events (SSE)
Server-Sent Events provide a simple way to push real-time updates from your server to the client.
Backend Implementation (Node.js/Express)
server/routes/streaming.js
const express = require('express');
const router = express.Router();
// SSE endpoint for real-time rates
router.get('/rates/stream', (req, res) => {
  // Set SSE headers
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Headers': 'Cache-Control'
  });
  // Send initial data
  const sendRates = () => {
    const rates = getCurrentRates(); // Your rate fetching logic
    res.write(`data: ${JSON.stringify(rates)}\n\n`);
  };
  // Send rates immediately
  sendRates();
  // Set up interval for updates
  const interval = setInterval(sendRates, 5000); // Update every 5 seconds
  // Clean up on client disconnect
  req.on('close', () => {
    clearInterval(interval);
    res.end();
  });
});
module.exports = router;Frontend Implementation (React)
components/RealTimeRates.jsx
import React, { useState, useEffect, useRef } from 'react';
const RealTimeRates = () => {
  const [rates, setRates] = useState({});
  const [connectionStatus, setConnectionStatus] = useState('connecting');
  const [lastUpdate, setLastUpdate] = useState(null);
  const eventSourceRef = useRef(null);
  useEffect(() => {
    const connectSSE = () => {
      const eventSource = new EventSource('/api/rates/stream');
      eventSourceRef.current = eventSource;
      eventSource.onopen = () => {
        setConnectionStatus('connected');
        console.log('SSE connection opened');
      };
      eventSource.onmessage = (event) => {
        try {
          const newRates = JSON.parse(event.data);
          setRates(newRates);
          setLastUpdate(new Date());
        } catch (error) {
          console.error('Error parsing SSE data:', error);
        }
      };
      eventSource.onerror = (error) => {
        console.error('SSE error:', error);
        setConnectionStatus('error');
        
        // Reconnect after 3 seconds
        setTimeout(() => {
          if (eventSource.readyState === EventSource.CLOSED) {
            connectSSE();
          }
        }, 3000);
      };
    };
    connectSSE();
    return () => {
      if (eventSourceRef.current) {
        eventSourceRef.current.close();
      }
    };
  }, []);
  return (
    <div className="p-6 bg-white rounded-lg shadow">
      <div className="flex items-center justify-between mb-4">
        <h3 className="text-lg font-semibold">Live Exchange Rates</h3>
        <div className="flex items-center">
          <div className={`w-2 h-2 rounded-full mr-2 ${
            connectionStatus === 'connected' ? 'bg-green-500' : 
            connectionStatus === 'error' ? 'bg-red-500' : 'bg-yellow-500'
          }`}></div>
          <span className="text-sm text-gray-600">{connectionStatus}</span>
        </div>
      </div>
      
      <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
        {Object.entries(rates).map(([currency, rate]) => (
          <div key={currency} className="p-3 bg-gray-50 rounded">
            <div className="text-sm font-medium text-gray-600">{currency}</div>
            <div className="text-lg font-bold">{rate}</div>
          </div>
        ))}
      </div>
      
      {lastUpdate && (
        <div className="mt-4 text-xs text-gray-500">
          Last updated: {lastUpdate.toLocaleTimeString()}
        </div>
      )}
    </div>
  );
};
export default RealTimeRates;2. WebSocket Implementation
WebSockets provide full-duplex communication, allowing both client and server to send messages at any time.
Backend Implementation (Node.js/Socket.io)
server/websocket.js
const socketIo = require('socket.io');
const setupWebSocket = (server) => {
  const io = socketIo(server, {
    cors: {
      origin: process.env.CLIENT_URL || "http://localhost:3000",
      methods: ["GET", "POST"]
    }
  });
  io.on('connection', (socket) => {
    console.log('Client connected:', socket.id);
    // Send initial rates
    socket.emit('rates-update', getCurrentRates());
    // Handle subscription to specific currencies
    socket.on('subscribe-currencies', (currencies) => {
      socket.join(`currencies-${currencies.join('-')}`);
      console.log(`Client ${socket.id} subscribed to:, currencies`);
    });
    // Handle client requests for rate updates
    socket.on('request-rates', (currencies) => {
      const rates = getCurrentRates(currencies);
      socket.emit('rates-update', rates);
    });
    socket.on('disconnect', () => {
      console.log('Client disconnected:', socket.id);
    });
  });
  // Broadcast rate updates every 5 seconds
  setInterval(() => {
    const rates = getCurrentRates();
    io.emit('rates-update', rates);
  }, 5000);
  return io;
};
module.exports = setupWebSocket;Frontend Implementation (React + Socket.io)
components/WebSocketRates.jsx
import React, { useState, useEffect, useRef } from 'react';
import io from 'socket.io-client';
const WebSocketRates = () => {
  const [rates, setRates] = useState({});
  const [connectionStatus, setConnectionStatus] = useState('connecting');
  const [subscribedCurrencies, setSubscribedCurrencies] = useState(['USD', 'EUR', 'GBP', 'JPY']);
  const socketRef = useRef(null);
  useEffect(() => {
    // Initialize socket connection
    socketRef.current = io(process.env.NEXT_PUBLIC_WS_URL || 'http://localhost:3001');
    const socket = socketRef.current;
    socket.on('connect', () => {
      setConnectionStatus('connected');
      console.log('WebSocket connected');
      
      // Subscribe to currencies
      socket.emit('subscribe-currencies', subscribedCurrencies);
    });
    socket.on('rates-update', (newRates) => {
      setRates(newRates);
    });
    socket.on('disconnect', () => {
      setConnectionStatus('disconnected');
      console.log('WebSocket disconnected');
    });
    socket.on('connect_error', (error) => {
      setConnectionStatus('error');
      console.error('WebSocket connection error:', error);
    });
    return () => {
      if (socket) {
        socket.disconnect();
      }
    };
  }, [subscribedCurrencies]);
  const requestRateUpdate = () => {
    if (socketRef.current && socketRef.current.connected) {
      socketRef.current.emit('request-rates', subscribedCurrencies);
    }
  };
  return (
    <div className="p-6 bg-white rounded-lg shadow">
      <div className="flex items-center justify-between mb-4">
        <h3 className="text-lg font-semibold">WebSocket Live Rates</h3>
        <div className="flex items-center space-x-2">
          <button 
            onClick={requestRateUpdate}
            className="px-3 py-1 bg-blue-500 text-white rounded text-sm"
            disabled={connectionStatus !== 'connected'}
          >
            Refresh
          </button>
          <div className="flex items-center">
            <div className={`w-2 h-2 rounded-full mr-2 ${
              connectionStatus === 'connected' ? 'bg-green-500' : 
              connectionStatus === 'error' ? 'bg-red-500' : 'bg-yellow-500'
            }`}></div>
            <span className="text-sm text-gray-600">{connectionStatus}</span>
          </div>
        </div>
      </div>
      
      <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
        {Object.entries(rates).map(([currency, rate]) => (
          <div key={currency} className="p-3 bg-gray-50 rounded">
            <div className="text-sm font-medium text-gray-600">{currency}</div>
            <div className="text-lg font-bold">{rate}</div>
          </div>
        ))}
      </div>
    </div>
  );
};
export default WebSocketRates;3. Smart Polling Strategy
Implement intelligent polling with exponential backoff and adaptive intervals based on market activity.
hooks/useSmartPolling.js
import { useState, useEffect, useRef, useCallback } from 'react';
const useSmartPolling = (fetchFunction, options = {}) => {
  const {
    initialInterval = 5000,
    maxInterval = 60000,
    backoffMultiplier = 1.5,
    maxRetries = 5,
    adaptiveThreshold = 0.01
  } = options;
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const [interval, setInterval] = useState(initialInterval);
  
  const timeoutRef = useRef(null);
  const retryCountRef = useRef(0);
  const lastDataRef = useRef(null);
  const poll = useCallback(async () => {
    setIsLoading(true);
    setError(null);
    try {
      const newData = await fetchFunction();
      
      // Check if data has changed significantly
      const hasSignificantChange = lastDataRef.current ? 
        hasDataChanged(lastDataRef.current, newData, adaptiveThreshold) : true;
      setData(newData);
      lastDataRef.current = newData;
      retryCountRef.current = 0;
      // Adaptive interval based on data changes
      if (hasSignificantChange) {
        // Data is changing, poll more frequently
        setInterval(Math.max(initialInterval, interval * 0.8));
      } else {
        // Data is stable, poll less frequently
        setInterval(Math.min(maxInterval, interval * 1.2));
      }
    } catch (err) {
      setError(err);
      retryCountRef.current += 1;
      // Exponential backoff on errors
      if (retryCountRef.current < maxRetries) {
        const backoffInterval = interval * Math.pow(backoffMultiplier, retryCountRef.current);
        setInterval(Math.min(maxInterval, backoffInterval));
      }
    } finally {
      setIsLoading(false);
    }
  }, [fetchFunction, interval, initialInterval, maxInterval, backoffMultiplier, maxRetries, adaptiveThreshold]);
  useEffect(() => {
    const scheduleNextPoll = () => {
      timeoutRef.current = setTimeout(() => {
        poll().then(scheduleNextPoll);
      }, interval);
    };
    // Initial poll
    poll().then(scheduleNextPoll);
    return () => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, [poll, interval]);
  return { data, isLoading, error, interval };
};
// Helper function to detect significant data changes
const hasDataChanged = (oldData, newData, threshold) => {
  if (!oldData || !newData) return true;
  for (const [currency, newRate] of Object.entries(newData)) {
    const oldRate = oldData[currency];
    if (!oldRate) return true;
    const changePercent = Math.abs((newRate - oldRate) / oldRate);
    if (changePercent > threshold) return true;
  }
  return false;
};
export default useSmartPolling;Best Practices
Performance Tips
- Use connection pooling for WebSocket connections
- Implement proper error handling and reconnection logic
- Use compression for data transmission
- Implement rate limiting to prevent abuse
- Monitor connection health and performance metrics
- Use adaptive polling intervals based on market volatility