본문으로 건너뛰기 Socket.IO Complete Guide | Real-Time Apps· Rooms

Socket.IO Complete Guide | Real-Time Apps· Rooms

Socket.IO Complete Guide | Real-Time Apps· Rooms

이 글의 핵심

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.


Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. Build real-time applications with Socket.IO. Covers WebSocket, Rooms, Broadcasting, Namespaces, and authentication with …

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers Socket.IO, WebSocket, Realtime, Node.js, Chat, Backend.