Socket.IO Complete Guide | Real-Time Apps, Rooms, Broadcasting

Socket.IO Complete Guide | Real-Time Apps, Rooms, Broadcasting

이 글의 핵심

Socket.IO is the standard library for real-time bidirectional communication in Node.js. This guide covers everything from basic emit/on patterns to production-ready rooms, namespaces, and authentication.

What This Guide Covers

Socket.IO enables real-time, bidirectional communication between browsers and servers. This guide covers the full stack — from a basic chat app to production patterns with rooms, namespaces, and Redis scaling.

Real-world insight: Replacing polling with Socket.IO reduced server load by 80% in a dashboard app — the difference between 60 requests/minute per user and a single persistent connection.


Setup

# Server
npm install socket.io express

# Client
npm install socket.io-client

1. Basic Server

import express from 'express';
import { createServer } from 'http';
import { Server } from 'socket.io';

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

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

  socket.on('message', (data) => {
    console.log('Received:', data);
    socket.emit('reply', { text: 'Got it!', timestamp: Date.now() });
  });

  socket.on('disconnect', () => {
    console.log('Client disconnected:', socket.id);
  });
});

httpServer.listen(3000);

2. Basic Client

import { io } from 'socket.io-client';

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

socket.on('connect', () => {
  console.log('Connected with id:', socket.id);
  socket.emit('message', { text: 'Hello server!' });
});

socket.on('reply', (data) => {
  console.log('Server says:', data.text);
});

socket.on('disconnect', () => {
  console.log('Disconnected');
});

3. Rooms

Rooms let you broadcast to a subset of connected clients — perfect for chat channels, game lobbies, or user-specific events.

io.on('connection', (socket) => {
  // Join a room
  socket.on('join-room', (roomId) => {
    socket.join(roomId);
    socket.to(roomId).emit('user-joined', { userId: socket.id });
  });

  // Send to a specific room
  socket.on('room-message', ({ roomId, message }) => {
    io.to(roomId).emit('new-message', {
      from: socket.id,
      message,
      timestamp: Date.now(),
    });
  });

  // Leave a room
  socket.on('leave-room', (roomId) => {
    socket.leave(roomId);
    socket.to(roomId).emit('user-left', { userId: socket.id });
  });
});

4. Broadcasting

MethodWho receives it
socket.emit()Sender only
socket.broadcast.emit()Everyone except sender
io.emit()Everyone including sender
io.to(room).emit()Everyone in the room
socket.to(room).emit()Everyone in the room except sender
io.on('connection', (socket) => {
  // Announce to everyone else when someone joins
  socket.broadcast.emit('user-online', { userId: socket.id });

  // Global announcement from server
  setInterval(() => {
    io.emit('server-time', { time: new Date().toISOString() });
  }, 5000);
});

5. Namespaces

Namespaces let you create isolated communication channels on the same server — useful for separating admin traffic from user traffic.

// Default namespace: /
io.on('connection', (socket) => {
  socket.emit('welcome', 'Public namespace');
});

// Admin namespace: /admin
const adminNs = io.of('/admin');
adminNs.on('connection', (socket) => {
  socket.emit('welcome', 'Admin namespace');
  socket.on('kick-user', (userId) => {
    // Only admins see this event
    adminNs.emit('user-kicked', userId);
  });
});

Client:

const userSocket = io('http://localhost:3000');          // /
const adminSocket = io('http://localhost:3000/admin');   // /admin

6. Acknowledgements

Acknowledgements give you request/response semantics over sockets.

// Server
socket.on('save-message', (message, callback) => {
  try {
    // Save to DB...
    callback({ status: 'ok', id: 'msg-123' });
  } catch (err) {
    callback({ status: 'error', message: err.message });
  }
});

// Client
socket.emit('save-message', { text: 'Hello' }, (response) => {
  if (response.status === 'ok') {
    console.log('Saved with id:', response.id);
  }
});

7. Authentication

Authenticate connections via middleware before they’re established:

import jwt from 'jsonwebtoken';

io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  if (!token) return next(new Error('Authentication required'));

  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET);
    socket.data.user = payload;
    next();
  } catch {
    next(new Error('Invalid token'));
  }
});

io.on('connection', (socket) => {
  console.log('Authenticated user:', socket.data.user.id);
});

Client sends token on connection:

const socket = io('http://localhost:3000', {
  auth: { token: localStorage.getItem('token') }
});

8. Scaling with Redis

For multiple Node.js instances, use the Redis adapter to share events:

npm install @socket.io/redis-adapter ioredis
import { createClient } from 'ioredis';
import { createAdapter } from '@socket.io/redis-adapter';

const pubClient = createClient({ host: 'localhost', port: 6379 });
const subClient = pubClient.duplicate();

io.adapter(createAdapter(pubClient, subClient));

Now io.emit() and room broadcasts work across all instances automatically.


React Integration

// hooks/useSocket.js
import { useEffect, useRef } from 'react';
import { io } from 'socket.io-client';

export function useSocket(url) {
  const socketRef = useRef(null);

  useEffect(() => {
    socketRef.current = io(url);
    return () => socketRef.current.disconnect();
  }, [url]);

  return socketRef.current;
}

// ChatRoom.jsx
export function ChatRoom({ roomId }) {
  const socket = useSocket('http://localhost:3000');
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    if (!socket) return;
    socket.emit('join-room', roomId);
    socket.on('new-message', (msg) => setMessages(prev => [...prev, msg]));
    return () => socket.off('new-message');
  }, [socket, roomId]);

  const send = (text) => socket.emit('room-message', { roomId, message: text });

  return (/* render messages */);
}

Key Takeaways

  • Rooms → group sockets for targeted broadcasts (chat channels, game sessions)
  • Namespaces → isolate feature areas on the same server
  • Acknowledgements → add request/response semantics when you need confirmation
  • Redis adapter → required for horizontal scaling
  • Middleware → handle auth once at connection time, not per-event

Socket.IO handles reconnection, fallbacks, and event buffering automatically — focus on your application logic, not transport reliability.