Real-Time Data Streaming with Claude Code

This guide explores patterns and best practices for building real-time applications with Claude Code, focusing on WebSocket and Server-Sent Events (SSE) integration for live data streaming, collaborative features, and responsive AI interactions.

Overview

Real-time data streaming with Claude Code enables:

  • Live AI response streaming with typewriter effects
  • Real-time collaboration features
  • Live data analysis and monitoring
  • Interactive dashboards with AI insights
  • Streaming code generation and execution

Streaming Technologies Comparison

Server-Sent Events (SSE)

  • Direction: Server → Client only
  • Protocol: HTTP/HTTPS
  • Reconnection: Automatic
  • Best for: Live updates, notifications, streaming AI responses

WebSockets

  • Direction: Bidirectional
  • Protocol: WS/WSS
  • Reconnection: Manual
  • Best for: Chat, collaboration, real-time interaction

Claude Streaming

  • Native Support: SSE-based streaming
  • Token Streaming: Real-time token generation
  • Tool Calls: Streaming execution updates

Implementation Patterns

1. Basic SSE Streaming with Claude

// server/claude-sse.ts
import { Anthropic } from '@anthropic-ai/sdk';
import express from 'express';
 
const app = express();
const anthropic = new Anthropic();
 
app.get('/api/claude-stream', async (req, res) => {
  // Set up SSE headers
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Access-Control-Allow-Origin': '*'
  });
 
  const { prompt } = req.query;
 
  try {
    const stream = await anthropic.messages.create({
      model: 'claude-3-sonnet-20240229',
      messages: [{ role: 'user', content: prompt as string }],
      max_tokens: 1024,
      stream: true
    });
 
    for await (const event of stream) {
      if (event.type === 'content_block_delta') {
        const data = {
          type: 'delta',
          content: event.delta.text,
          index: event.index
        };
        res.write(`data: ${JSON.stringify(data)}\n\n`);
      } else if (event.type === 'message_stop') {
        res.write('data: [DONE]\n\n');
        res.end();
      }
    }
  } catch (error) {
    res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
    res.end();
  }
});
 
// Client-side consumption
class ClaudeSSEClient {
  private eventSource: EventSource | null = null;
  
  connect(prompt: string, onMessage: (data: any) => void) {
    const url = `/api/claude-stream?prompt=${encodeURIComponent(prompt)}`;
    this.eventSource = new EventSource(url);
    
    this.eventSource.onmessage = (event) => {
      if (event.data === '[DONE]') {
        this.disconnect();
        return;
      }
      
      const data = JSON.parse(event.data);
      onMessage(data);
    };
    
    this.eventSource.onerror = (error) => {
      console.error('SSE error:', error);
      this.disconnect();
    };
  }
  
  disconnect() {
    if (this.eventSource) {
      this.eventSource.close();
      this.eventSource = null;
    }
  }
}

2. WebSocket Integration for Bidirectional Communication

// server/claude-websocket.ts
import { WebSocketServer } from 'ws';
import { Anthropic } from '@anthropic-ai/sdk';
 
interface ClaudeSession {
  id: string;
  anthropic: Anthropic;
  context: any[];
  activeStream?: any;
}
 
class ClaudeWebSocketServer {
  private wss: WebSocketServer;
  private sessions: Map<string, ClaudeSession> = new Map();
  
  constructor(port: number) {
    this.wss = new WebSocketServer({ port });
    this.setupHandlers();
  }
  
  private setupHandlers() {
    this.wss.on('connection', (ws) => {
      const sessionId = this.generateSessionId();
      const session: ClaudeSession = {
        id: sessionId,
        anthropic: new Anthropic(),
        context: []
      };
      
      this.sessions.set(sessionId, session);
      
      ws.on('message', async (data) => {
        const message = JSON.parse(data.toString());
        await this.handleMessage(ws, session, message);
      });
      
      ws.on('close', () => {
        // Clean up session
        if (session.activeStream) {
          session.activeStream.controller?.abort();
        }
        this.sessions.delete(sessionId);
      });
      
      // Send initial connection confirmation
      ws.send(JSON.stringify({
        type: 'connected',
        sessionId
      }));
    });
  }
  
  private async handleMessage(ws: any, session: ClaudeSession, message: any) {
    switch (message.type) {
      case 'query':
        await this.handleQuery(ws, session, message);
        break;
      case 'stop':
        if (session.activeStream) {
          session.activeStream.controller?.abort();
        }
        break;
      case 'context':
        session.context = message.context || [];
        break;
    }
  }
  
  private async handleQuery(ws: any, session: ClaudeSession, message: any) {
    try {
      const controller = new AbortController();
      
      const stream = await session.anthropic.messages.create({
        model: 'claude-3-sonnet-20240229',
        messages: [
          ...session.context,
          { role: 'user', content: message.prompt }
        ],
        max_tokens: 1024,
        stream: true
      }, {
        signal: controller.signal
      });
      
      session.activeStream = { stream, controller };
      
      for await (const event of stream) {
        if (event.type === 'content_block_delta') {
          ws.send(JSON.stringify({
            type: 'delta',
            content: event.delta.text,
            index: event.index
          }));
        } else if (event.type === 'message_stop') {
          ws.send(JSON.stringify({
            type: 'complete',
            usage: event.usage
          }));
          
          // Update context
          session.context.push(
            { role: 'user', content: message.prompt },
            { role: 'assistant', content: event.message.content }
          );
        }
      }
    } catch (error) {
      ws.send(JSON.stringify({
        type: 'error',
        error: error.message
      }));
    }
  }
  
  private generateSessionId(): string {
    return Math.random().toString(36).substring(7);
  }
}
 
// Client-side WebSocket handler
class ClaudeWebSocketClient {
  private ws: WebSocket | null = null;
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 5;
  
  connect(url: string, handlers: {
    onMessage?: (data: any) => void;
    onError?: (error: any) => void;
    onConnect?: (sessionId: string) => void;
  }) {
    this.ws = new WebSocket(url);
    
    this.ws.onopen = () => {
      this.reconnectAttempts = 0;
      console.log('WebSocket connected');
    };
    
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      
      switch (data.type) {
        case 'connected':
          handlers.onConnect?.(data.sessionId);
          break;
        case 'delta':
        case 'complete':
          handlers.onMessage?.(data);
          break;
        case 'error':
          handlers.onError?.(data.error);
          break;
      }
    };
    
    this.ws.onerror = (error) => {
      handlers.onError?.(error);
    };
    
    this.ws.onclose = () => {
      this.handleReconnect(url, handlers);
    };
  }
  
  private handleReconnect(url: string, handlers: any) {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++;
      const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 10000);
      
      setTimeout(() => {
        console.log(`Reconnecting... (attempt ${this.reconnectAttempts})`);
        this.connect(url, handlers);
      }, delay);
    }
  }
  
  sendQuery(prompt: string) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({
        type: 'query',
        prompt
      }));
    }
  }
  
  stop() {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({ type: 'stop' }));
    }
  }
  
  disconnect() {
    if (this.ws) {
      this.ws.close();
      this.ws = null;
    }
  }
}

3. Real-Time Collaboration Pattern

// Collaborative editing with Claude assistance
interface CollaborativeSession {
  documentId: string;
  participants: Map<string, Participant>;
  document: Document;
  claudeContext: any[];
}
 
class CollaborativeClaudeEditor {
  private sessions: Map<string, CollaborativeSession> = new Map();
  private anthropic: Anthropic;
  
  constructor() {
    this.anthropic = new Anthropic();
  }
  
  async handleCollaborativeAction(
    sessionId: string,
    userId: string,
    action: any
  ) {
    const session = this.sessions.get(sessionId);
    if (!session) return;
    
    switch (action.type) {
      case 'edit':
        // Apply edit
        this.applyEdit(session, action.edit);
        
        // Broadcast to other participants
        this.broadcast(session, userId, {
          type: 'edit',
          edit: action.edit,
          userId
        });
        break;
        
      case 'ai-assist':
        // Stream AI assistance to all participants
        await this.streamAIAssistance(session, action.prompt);
        break;
        
      case 'cursor-move':
        // Update cursor position
        this.updateCursor(session, userId, action.position);
        break;
    }
  }
  
  private async streamAIAssistance(
    session: CollaborativeSession,
    prompt: string
  ) {
    const stream = await this.anthropic.messages.create({
      model: 'claude-3-sonnet-20240229',
      messages: [
        {
          role: 'system',
          content: 'You are assisting with collaborative document editing.'
        },
        ...session.claudeContext,
        {
          role: 'user',
          content: `Document context: ${session.document.content}\n\nRequest: ${prompt}`
        }
      ],
      stream: true
    });
    
    let fullResponse = '';
    
    for await (const event of stream) {
      if (event.type === 'content_block_delta') {
        fullResponse += event.delta.text;
        
        // Broadcast delta to all participants
        this.broadcastToAll(session, {
          type: 'ai-delta',
          content: event.delta.text
        });
      }
    }
    
    // Update context
    session.claudeContext.push(
      { role: 'user', content: prompt },
      { role: 'assistant', content: fullResponse }
    );
  }
  
  private broadcast(
    session: CollaborativeSession,
    excludeUserId: string,
    message: any
  ) {
    session.participants.forEach((participant, userId) => {
      if (userId !== excludeUserId && participant.ws) {
        participant.ws.send(JSON.stringify(message));
      }
    });
  }
  
  private broadcastToAll(session: CollaborativeSession, message: any) {
    session.participants.forEach((participant) => {
      if (participant.ws) {
        participant.ws.send(JSON.stringify(message));
      }
    });
  }
}

4. Live Data Analysis Pattern

// Real-time data analysis with Claude
class LiveDataAnalyzer {
  private dataStream: EventSource;
  private analysisQueue: any[] = [];
  private batchSize = 10;
  private analysisInterval = 5000; // 5 seconds
  private anthropic: Anthropic;
  
  constructor(dataStreamUrl: string) {
    this.anthropic = new Anthropic();
    this.dataStream = new EventSource(dataStreamUrl);
    this.setupDataStream();
    this.startAnalysisLoop();
  }
  
  private setupDataStream() {
    this.dataStream.onmessage = (event) => {
      const data = JSON.parse(event.data);
      this.analysisQueue.push(data);
      
      // Trigger immediate analysis for critical data
      if (data.priority === 'critical') {
        this.analyzeImmediate(data);
      }
    };
  }
  
  private async startAnalysisLoop() {
    setInterval(async () => {
      if (this.analysisQueue.length >= this.batchSize) {
        const batch = this.analysisQueue.splice(0, this.batchSize);
        await this.analyzeBatch(batch);
      }
    }, this.analysisInterval);
  }
  
  private async analyzeBatch(batch: any[]) {
    const prompt = `Analyze the following real-time data batch and identify patterns, anomalies, and insights:
    
    ${JSON.stringify(batch, null, 2)}
    
    Provide:
    1. Key patterns observed
    2. Any anomalies detected
    3. Recommended actions
    4. Predictions for next data points`;
    
    try {
      const response = await this.anthropic.messages.create({
        model: 'claude-3-sonnet-20240229',
        messages: [{ role: 'user', content: prompt }],
        max_tokens: 500
      });
      
      this.broadcastAnalysis({
        timestamp: new Date().toISOString(),
        batchSize: batch.length,
        analysis: response.content[0].text,
        dataRange: {
          start: batch[0].timestamp,
          end: batch[batch.length - 1].timestamp
        }
      });
    } catch (error) {
      console.error('Analysis error:', error);
    }
  }
  
  private async analyzeImmediate(data: any) {
    const prompt = `CRITICAL DATA ALERT: Analyze this data point immediately:
    ${JSON.stringify(data, null, 2)}
    
    Provide immediate assessment and recommended actions.`;
    
    const response = await this.anthropic.messages.create({
      model: 'claude-3-opus-20240229', // Use faster model for critical
      messages: [{ role: 'user', content: prompt }],
      max_tokens: 200
    });
    
    this.broadcastAlert({
      type: 'critical',
      data,
      analysis: response.content[0].text,
      timestamp: new Date().toISOString()
    });
  }
  
  private broadcastAnalysis(analysis: any) {
    // Broadcast to connected clients via WebSocket/SSE
    console.log('Broadcasting analysis:', analysis);
  }
  
  private broadcastAlert(alert: any) {
    // Broadcast critical alerts
    console.log('Broadcasting alert:', alert);
  }
}

5. Streaming Code Generation

// Real-time code generation with execution feedback
class StreamingCodeGenerator {
  private anthropic: Anthropic;
  private executionEnvironment: CodeExecutor;
  
  async generateAndExecute(
    specification: string,
    onUpdate: (update: any) => void
  ) {
    // Stream code generation
    const stream = await this.anthropic.messages.create({
      model: 'claude-3-sonnet-20240229',
      messages: [{
        role: 'user',
        content: `Generate TypeScript code for: ${specification}`
      }],
      stream: true
    });
    
    let codeBuffer = '';
    let inCodeBlock = false;
    
    for await (const event of stream) {
      if (event.type === 'content_block_delta') {
        const text = event.delta.text;
        codeBuffer += text;
        
        // Detect code blocks
        if (text.includes('```typescript')) {
          inCodeBlock = true;
          onUpdate({ type: 'code_start' });
        } else if (text.includes('```') && inCodeBlock) {
          inCodeBlock = false;
          
          // Extract and execute code
          const code = this.extractCode(codeBuffer);
          onUpdate({ type: 'code_complete', code });
          
          // Execute in sandbox
          const result = await this.executionEnvironment.execute(code);
          onUpdate({ type: 'execution_result', result });
          
          codeBuffer = '';
        } else if (inCodeBlock) {
          onUpdate({ type: 'code_delta', delta: text });
        } else {
          onUpdate({ type: 'text_delta', delta: text });
        }
      }
    }
  }
  
  private extractCode(buffer: string): string {
    const match = buffer.match(/```typescript\n([\s\S]*?)\n```/);
    return match ? match[1] : '';
  }
}

Best Practices

1. Connection Management

  • Implement automatic reconnection for WebSockets
  • Use exponential backoff for retry attempts
  • Handle connection state in UI
  • Provide offline functionality

2. Performance Optimization

  • Batch updates to reduce render cycles
  • Implement virtual scrolling for long streams
  • Use Web Workers for heavy processing
  • Throttle/debounce user inputs

3. Error Handling

class StreamErrorHandler {
  private errorQueue: Error[] = [];
  private maxRetries = 3;
  
  async handleStreamError(
    error: Error,
    retryFn: () => Promise<any>,
    attempt = 1
  ): Promise<any> {
    this.errorQueue.push(error);
    
    if (error.message.includes('rate_limit')) {
      // Handle rate limiting
      const delay = this.calculateRateLimitDelay(error);
      await this.delay(delay);
      return retryFn();
    } else if (error.message.includes('timeout')) {
      // Handle timeouts with exponential backoff
      if (attempt <= this.maxRetries) {
        const delay = Math.pow(2, attempt) * 1000;
        await this.delay(delay);
        return this.handleStreamError(error, retryFn, attempt + 1);
      }
    }
    
    throw error;
  }
  
  private calculateRateLimitDelay(error: Error): number {
    // Extract retry-after header or use default
    return 60000; // 1 minute default
  }
  
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

4. Security Considerations

  • Validate all incoming messages
  • Implement authentication for WebSocket connections
  • Use HTTPS/WSS in production
  • Rate limit client requests
  • Sanitize AI responses before rendering

5. Testing Strategies

// Mock streaming for tests
class MockClaudeStream {
  async *streamResponse(response: string, chunkSize = 10) {
    for (let i = 0; i < response.length; i += chunkSize) {
      yield {
        type: 'content_block_delta',
        delta: {
          text: response.slice(i, i + chunkSize)
        }
      };
      
      // Simulate network delay
      await new Promise(resolve => setTimeout(resolve, 50));
    }
    
    yield { type: 'message_stop' };
  }
}

UI/UX Patterns

Typewriter Effect

class TypewriterRenderer {
  private container: HTMLElement;
  private speed = 30; // ms per character
  
  async render(text: string) {
    this.container.innerHTML = '';
    
    for (const char of text) {
      this.container.innerHTML += char;
      await new Promise(resolve => setTimeout(resolve, this.speed));
    }
  }
  
  async renderStream(stream: AsyncIterable<any>) {
    for await (const event of stream) {
      if (event.type === 'content_block_delta') {
        for (const char of event.delta.text) {
          this.container.innerHTML += char;
          await new Promise(resolve => setTimeout(resolve, this.speed));
        }
      }
    }
  }
}

Progress Indicators

interface StreamProgress {
  tokensGenerated: number;
  estimatedTotal: number;
  timeElapsed: number;
}
 
class StreamProgressTracker {
  private startTime: number;
  private tokensGenerated = 0;
  
  start() {
    this.startTime = Date.now();
    this.tokensGenerated = 0;
  }
  
  update(tokens: number): StreamProgress {
    this.tokensGenerated += tokens;
    const timeElapsed = Date.now() - this.startTime;
    const tokensPerSecond = this.tokensGenerated / (timeElapsed / 1000);
    
    return {
      tokensGenerated: this.tokensGenerated,
      estimatedTotal: Math.round(this.tokensGenerated * 1.2), // Estimate
      timeElapsed
    };
  }
}

Conclusion

Real-time data streaming with Claude Code opens up possibilities for interactive AI applications. Whether using SSE for simple streaming or WebSockets for complex bidirectional communication, these patterns provide a foundation for building responsive, real-time AI experiences.

See Also

External Resources