Skip to content

Socket.IO Examples

Practical examples for using Response Handler with Socket.IO.

Basic Socket.IO Server

typescript
import { createServer } from 'http';
import { Server } from 'socket.io';
import { quickSocketSetup } from '@amit-kandar/response-handler';

const httpServer = createServer();
const io = new Server(httpServer, {
  cors: {
    origin: 'http://localhost:3000',
    methods: ['GET', 'POST'],
  },
});

const { enhance, wrapper } = quickSocketSetup({
  mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
  logging: { enabled: true, logErrors: true },
});

io.on('connection', (socket) => {
  console.log('Client connected:', socket.id);

  // Basic event handling
  socket.on('get-status', (data) => {
    const response = enhance(socket, 'status-response');
    response.ok({ status: 'online', timestamp: Date.now() }, 'Status retrieved');
  });
});

httpServer.listen(3001, () => {
  console.log('Socket.IO server running on port 3001');
});

Real-time Chat Application

typescript
import { Server } from 'socket.io';
import { quickSocketSetup } from '@amit-kandar/response-handler';

const io = new Server(httpServer);
const { enhance, wrapper } = quickSocketSetup({
  mode: 'development',
  logging: { enabled: true },
});

// Store connected users
const connectedUsers = new Map();

io.on('connection', (socket) => {
  // User authentication
  socket.on(
    'authenticate',
    wrapper(async (socket, response, data) => {
      const { token, username } = data;

      if (!token || !username) {
        return response.badRequest(
          { missingFields: ['token', 'username'] },
          'Authentication credentials required',
        );
      }

      try {
        const user = await validateToken(token);
        socket.userId = user.id;
        socket.username = username;

        connectedUsers.set(socket.id, {
          id: user.id,
          username,
          joinedAt: new Date().toISOString(),
        });

        response.ok(
          {
            id: user.id,
            username,
            connectedUsers: Array.from(connectedUsers.values()),
          },
          'Authentication successful',
        );

        // Notify others
        socket.broadcast.emit('user-joined', {
          id: user.id,
          username,
          timestamp: new Date().toISOString(),
        });
      } catch (error) {
        response.unauthorized(error, 'Invalid authentication token');
      }
    }),
  );

  // Join chat room
  socket.on(
    'join-room',
    wrapper(async (socket, response, data) => {
      const { roomId } = data;

      if (!socket.userId) {
        return response.unauthorized(null, 'Please authenticate first');
      }

      if (!roomId) {
        return response.badRequest({ field: 'roomId' }, 'Room ID required');
      }

      await socket.join(roomId);
      socket.currentRoom = roomId;

      response.ok({ roomId }, `Joined room ${roomId}`);

      // Notify room members
      response.toRoom(roomId).emit('user-joined-room', {
        userId: socket.userId,
        username: socket.username,
        roomId,
        timestamp: new Date().toISOString(),
      });
    }),
  );

  // Send message
  socket.on(
    'send-message',
    wrapper(async (socket, response, data) => {
      const { message, roomId } = data;

      if (!socket.userId) {
        return response.unauthorized(null, 'Please authenticate first');
      }

      if (!message || !roomId) {
        return response.badRequest(
          { missingFields: ['message', 'roomId'] },
          'Message and room ID required',
        );
      }

      const messageObj = {
        id: generateMessageId(),
        message,
        userId: socket.userId,
        username: socket.username,
        roomId,
        timestamp: new Date().toISOString(),
      };

      // Save to database (optional)
      await saveMessage(messageObj);

      // Broadcast to room
      response.toRoom(roomId).emit('new-message', messageObj);

      // Confirm to sender
      response.ok({ messageId: messageObj.id }, 'Message sent');
    }),
  );

  // Private message
  socket.on(
    'send-private-message',
    wrapper(async (socket, response, data) => {
      const { message, targetUserId } = data;

      const targetSocket = findSocketByUserId(targetUserId);
      if (!targetSocket) {
        return response.notFound({ userId: targetUserId }, 'User not online');
      }

      const privateMessage = {
        id: generateMessageId(),
        message,
        from: {
          id: socket.userId,
          username: socket.username,
        },
        timestamp: new Date().toISOString(),
      };

      // Send to target user
      response.toSocket(targetSocket.id).emit('private-message', privateMessage);

      // Confirm to sender
      response.ok({ messageId: privateMessage.id }, 'Private message sent');
    }),
  );

  // Disconnect handling
  socket.on('disconnect', () => {
    connectedUsers.delete(socket.id);

    if (socket.userId && socket.username) {
      socket.broadcast.emit('user-left', {
        id: socket.userId,
        username: socket.username,
        timestamp: new Date().toISOString(),
      });
    }
  });
});

// Helper functions
function generateMessageId() {
  return `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}

function findSocketByUserId(userId) {
  for (const [socketId, socketObj] of io.sockets.sockets) {
    if (socketObj.userId === userId) {
      return socketObj;
    }
  }
  return null;
}

Gaming Server Example

typescript
import { Server } from 'socket.io';
import { quickSocketSetup } from '@amit-kandar/response-handler';

const io = new Server(httpServer);
const { enhance, wrapper } = quickSocketSetup({
  mode: 'development',
  logging: { enabled: true },
});

// Game state
const games = new Map();
const waitingPlayers = [];

io.on('connection', (socket) => {
  // Join game queue
  socket.on(
    'join-queue',
    wrapper(async (socket, response, data) => {
      const { playerName, gameType } = data;

      if (!playerName) {
        return response.badRequest({ field: 'playerName' }, 'Player name required');
      }

      const player = {
        id: socket.id,
        name: playerName,
        gameType: gameType || 'standard',
        joinedAt: Date.now(),
      };

      waitingPlayers.push(player);
      socket.playerInfo = player;

      response.ok({ position: waitingPlayers.length }, 'Added to queue');

      // Try to match players
      tryMatchPlayers();
    }),
  );

  // Make game move
  socket.on(
    'make-move',
    wrapper(async (socket, response, data) => {
      const { gameId, move } = data;

      if (!socket.gameId) {
        return response.badRequest(null, 'Not in a game');
      }

      const game = games.get(gameId);
      if (!game) {
        return response.notFound({ gameId }, 'Game not found');
      }

      // Validate move
      if (!isValidMove(game, socket.id, move)) {
        return response.badRequest({ move }, 'Invalid move');
      }

      // Apply move
      applyMove(game, socket.id, move);

      // Check game state
      const gameState = checkGameState(game);

      // Broadcast game update
      response.toRoom(`game-${gameId}`).emit('game-update', {
        gameId,
        move,
        playerId: socket.id,
        gameState: game.state,
        timestamp: Date.now(),
      });

      response.ok({ gameState }, 'Move accepted');

      // Handle game end
      if (gameState.finished) {
        endGame(game);
      }
    }),
  );

  // Leave game
  socket.on(
    'leave-game',
    wrapper(async (socket, response, data) => {
      if (socket.gameId) {
        const game = games.get(socket.gameId);
        if (game) {
          // Notify other players
          response.toRoom(`game-${socket.gameId}`).emit('player-left', {
            playerId: socket.id,
            timestamp: Date.now(),
          });

          // End game
          endGame(game);
        }

        socket.leave(`game-${socket.gameId}`);
        socket.gameId = null;
      }

      response.ok(null, 'Left game');
    }),
  );

  socket.on('disconnect', () => {
    // Remove from waiting queue
    const index = waitingPlayers.findIndex((p) => p.id === socket.id);
    if (index !== -1) {
      waitingPlayers.splice(index, 1);
    }

    // Handle game disconnect
    if (socket.gameId) {
      const game = games.get(socket.gameId);
      if (game) {
        socket.to(`game-${socket.gameId}`).emit('player-disconnected', {
          playerId: socket.id,
          timestamp: Date.now(),
        });
        endGame(game);
      }
    }
  });
});

function tryMatchPlayers() {
  while (waitingPlayers.length >= 2) {
    const player1 = waitingPlayers.shift();
    const player2 = waitingPlayers.shift();

    createGame(player1, player2);
  }
}

function createGame(player1, player2) {
  const gameId = `game-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;

  const game = {
    id: gameId,
    players: [player1, player2],
    state: initializeGameState(),
    createdAt: Date.now(),
    status: 'active',
  };

  games.set(gameId, game);

  // Join game room
  const socket1 = io.sockets.sockets.get(player1.id);
  const socket2 = io.sockets.sockets.get(player2.id);

  if (socket1 && socket2) {
    socket1.join(`game-${gameId}`);
    socket2.join(`game-${gameId}`);
    socket1.gameId = gameId;
    socket2.gameId = gameId;

    // Notify players
    io.to(`game-${gameId}`).emit('game-started', {
      gameId,
      players: [player1, player2],
      gameState: game.state,
      timestamp: Date.now(),
    });
  }
}

Real-time Collaboration Example

typescript
import { Server } from 'socket.io';
import { quickSocketSetup } from '@amit-kandar/response-handler';

const io = new Server(httpServer);
const { enhance, wrapper } = quickSocketSetup({
  mode: 'development',
  logging: { enabled: true },
});

// Document state
const documents = new Map();

io.on('connection', (socket) => {
  // Join document
  socket.on(
    'join-document',
    wrapper(async (socket, response, data) => {
      const { documentId, userId } = data;

      if (!documentId || !userId) {
        return response.badRequest(
          { missingFields: ['documentId', 'userId'] },
          'Document ID and user ID required',
        );
      }

      // Get or create document
      let doc = documents.get(documentId);
      if (!doc) {
        doc = {
          id: documentId,
          content: '',
          collaborators: new Map(),
          operations: [],
          version: 0,
        };
        documents.set(documentId, doc);
      }

      // Add collaborator
      doc.collaborators.set(socket.id, {
        userId,
        joinedAt: Date.now(),
        cursor: { line: 0, column: 0 },
      });

      socket.join(`doc-${documentId}`);
      socket.documentId = documentId;
      socket.userId = userId;

      response.ok(
        {
          document: {
            id: documentId,
            content: doc.content,
            version: doc.version,
            collaborators: Array.from(doc.collaborators.values()),
          },
        },
        'Joined document',
      );

      // Notify other collaborators
      response.toRoom(`doc-${documentId}`).emit('collaborator-joined', {
        userId,
        socketId: socket.id,
        timestamp: Date.now(),
      });
    }),
  );

  // Text operation
  socket.on(
    'text-operation',
    wrapper(async (socket, response, data) => {
      const { operation, version } = data;

      if (!socket.documentId) {
        return response.badRequest(null, 'Not in a document');
      }

      const doc = documents.get(socket.documentId);
      if (!doc) {
        return response.notFound({ documentId: socket.documentId }, 'Document not found');
      }

      // Version check for operational transformation
      if (version !== doc.version) {
        return response.conflict(
          { expectedVersion: doc.version, receivedVersion: version },
          'Version conflict - please refresh',
        );
      }

      // Apply operation
      try {
        const transformedOp = applyOperation(doc, operation);
        doc.version++;

        // Broadcast to other collaborators
        response.toRoom(`doc-${socket.documentId}`).emit('operation-applied', {
          operation: transformedOp,
          version: doc.version,
          authorId: socket.userId,
          timestamp: Date.now(),
        });

        response.ok(
          {
            version: doc.version,
            operation: transformedOp,
          },
          'Operation applied',
        );
      } catch (error) {
        response.badRequest(error, 'Invalid operation');
      }
    }),
  );

  // Cursor position update
  socket.on('cursor-update', (data) => {
    if (!socket.documentId) return;

    const doc = documents.get(socket.documentId);
    if (doc && doc.collaborators.has(socket.id)) {
      const collaborator = doc.collaborators.get(socket.id);
      collaborator.cursor = data.cursor;

      // Broadcast cursor position
      socket.to(`doc-${socket.documentId}`).emit('cursor-moved', {
        userId: socket.userId,
        cursor: data.cursor,
        timestamp: Date.now(),
      });
    }
  });

  // Selection update
  socket.on('selection-update', (data) => {
    if (!socket.documentId) return;

    socket.to(`doc-${socket.documentId}`).emit('selection-changed', {
      userId: socket.userId,
      selection: data.selection,
      timestamp: Date.now(),
    });
  });

  socket.on('disconnect', () => {
    if (socket.documentId) {
      const doc = documents.get(socket.documentId);
      if (doc) {
        doc.collaborators.delete(socket.id);

        // Notify other collaborators
        socket.to(`doc-${socket.documentId}`).emit('collaborator-left', {
          userId: socket.userId,
          timestamp: Date.now(),
        });

        // Clean up empty documents
        if (doc.collaborators.size === 0) {
          documents.delete(socket.documentId);
        }
      }
    }
  });
});

function applyOperation(doc, operation) {
  // Simplified operational transformation
  switch (operation.type) {
    case 'insert':
      doc.content =
        doc.content.slice(0, operation.position) +
        operation.text +
        doc.content.slice(operation.position);
      break;
    case 'delete':
      doc.content =
        doc.content.slice(0, operation.position) +
        doc.content.slice(operation.position + operation.length);
      break;
    default:
      throw new Error('Unknown operation type');
  }

  doc.operations.push(operation);
  return operation;
}

Socket.IO Client Examples

Basic Client

typescript
import io from 'socket.io-client';

const socket = io('http://localhost:3001');

socket.on('connect', () => {
  console.log('Connected to server');

  // Send authentication
  socket.emit('authenticate', {
    token: 'your-jwt-token',
    username: 'john_doe',
  });
});

socket.on('auth-result', (data) => {
  if (data.success) {
    console.log('Authenticated successfully:', data.data);
  } else {
    console.error('Authentication failed:', data.message);
  }
});

socket.on('new-message', (message) => {
  console.log('New message:', message);
});

React Client

typescript
import React, { useEffect, useState } from 'react';
import io from 'socket.io-client';

function ChatApp() {
  const [socket, setSocket] = useState(null);
  const [messages, setMessages] = useState([]);
  const [newMessage, setNewMessage] = useState('');

  useEffect(() => {
    const newSocket = io('http://localhost:3001');
    setSocket(newSocket);

    newSocket.on('new-message', (message) => {
      setMessages(prev => [...prev, message]);
    });

    return () => newSocket.close();
  }, []);

  const sendMessage = () => {
    if (socket && newMessage.trim()) {
      socket.emit('send-message', {
        message: newMessage,
        roomId: 'general'
      });
      setNewMessage('');
    }
  };

  return (
    <div>
      <div>
        {messages.map(msg => (
          <div key={msg.id}>
            <strong>{msg.username}:</strong> {msg.message}
          </div>
        ))}
      </div>
      <input
        value={newMessage}
        onChange={(e) => setNewMessage(e.target.value)}
        onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
      />
      <button onClick={sendMessage}>Send</button>
    </div>
  );
}

Error Handling Patterns

typescript
// Centralized error handling
socket.on('error-event', (data) => {
  switch (data.error.type) {
    case 'ValidationError':
      showValidationError(data.error.message);
      break;
    case 'AuthenticationError':
      redirectToLogin();
      break;
    case 'RateLimitError':
      showRateLimitWarning();
      break;
    default:
      showGenericError(data.message);
  }
});

// Retry logic
function sendMessageWithRetry(messageData, maxRetries = 3) {
  let retries = 0;

  const send = () => {
    socket.emit('send-message', messageData);

    const timeout = setTimeout(
      () => {
        if (retries < maxRetries) {
          retries++;
          console.log(`Retry attempt ${retries}`);
          send();
        } else {
          console.error('Max retries reached');
        }
      },
      1000 * Math.pow(2, retries),
    ); // Exponential backoff

    socket.once('message-sent', () => {
      clearTimeout(timeout);
    });
  };

  send();
}

This comprehensive set of examples covers the most common Socket.IO use cases with Response Handler, providing a solid foundation for building real-time applications.

Released under the ISC License.