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
| Method | Who 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.