stack-analysis
CHAPTER 34 / 90
읽기 약 2분
FUNCTION
실시간: Socket.io 프로덕션 패턴
핵심 개념
Socket.io·룸·인증·Redis adapter — 채팅·알림·라이브 업데이트.
본문
기본 셋업
import { Server } from 'socket.io';
import { createServer } from 'http';
import express from 'express';
import jwt from 'jsonwebtoken';
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: { origin: process.env.FRONTEND_URL },
});
// 인증 미들웨어
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) return next(new Error('No token'));
try {
const payload = jwt.verify(token, process.env.JWT_ACCESS_SECRET!) as any;
socket.data.userId = payload.sub;
next();
} catch {
next(new Error('Invalid token'));
}
});
io.on('connection', (socket) => {
console.log('User connected:', socket.data.userId);
// 룸 입장
socket.on('join-room', async (roomId) => {
const allowed = await canAccessRoom(socket.data.userId, roomId);
if (!allowed) return socket.emit('error', 'Forbidden');
socket.join(`room:${roomId}`);
});
// 메시지 전송
socket.on('message', async (data) => {
const msg = await db.message.create({
data: { content: data.content, userId: socket.data.userId, roomId: data.roomId },
});
io.to(`room:${data.roomId}`).emit('message', msg);
});
socket.on('disconnect', () => {
console.log('User disconnected');
});
});Redis Adapter (수평 확장)
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));
// 이제 여러 서버 인스턴스가 룸을 공유
// 서버 A의 io.to(room).emit() → 서버 B의 클라이언트에게도 도달클라이언트
import { io as ioClient } from 'socket.io-client';
const socket = ioClient(process.env.NEXT_PUBLIC_API_URL!, {
auth: { token: localStorage.getItem('accessToken') },
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
});
socket.on('connect', () => console.log('Connected'));
socket.on('disconnect', () => console.log('Disconnected'));
socket.on('error', (err) => console.error(err));
// 룸 입장
socket.emit('join-room', roomId);
// 메시지 수신
socket.on('message', (msg) => {
setMessages(prev => [...prev, msg]);
});
// 메시지 전송
socket.emit('message', { roomId, content: 'Hello' });알림 패턴
// 서버 — 특정 사용자에게 알림
function notifyUser(userId: string, notification: Notification) {
io.to(`user:${userId}`).emit('notification', notification);
}
// 사용자가 connect 시 자동 룸 입장
io.on('connection', (socket) => {
socket.join(`user:${socket.data.userId}`);
});
// 사용
async function likePost(postId: string, fromUserId: string) {
const post = await db.post.findUnique({ where: { id: postId } });
if (post.authorId !== fromUserId) {
notifyUser(post.authorId, {
type: 'like',
postId,
fromUserId,
createdAt: new Date(),
});
}
}룸 vs 네임스페이스
네임스페이스 (Namespace):
- 완전 분리된 채널 (/, /admin, /chat 등)
- 다른 미들웨어 적용 가능
룸 (Room):
- 같은 네임스페이스 내 그룹
- 동적 생성·소멸
- 채팅방, 게임방 등에 적합
// 네임스페이스
const adminIo = io.of('/admin');
adminIo.use(adminAuth);
// 룸
socket.join('room:123');
io.to('room:123').emit('message', msg);메모리·연결 관리
// 자동 cleanup
io.on('connection', (socket) => {
const interval = setInterval(() => socket.emit('ping'), 30000);
socket.on('disconnect', () => {
clearInterval(interval);
// DB에 user 상태 업데이트
db.user.update({
where: { id: socket.data.userId },
data: { lastSeenAt: new Date() },
});
});
});다음 챕터
CH.35 "작업 큐: BullMQ + Redis".
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 코드의 실시간 통신 부분을 분석해서 실전 분석 + 개선 우선순위를 알려줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
실시간 통신 관련 인기 라이브러리/패턴 5개를 비교 분석해서 패턴 추출를 알려줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 프로젝트 전체에서 실시간 통신 최적화 가능 위치를 보고해줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 백엔드 시장의 실시간 통신 트렌드를 솔직히 알려줘.
⭐ 이것만 기억하세요
실시간: Socket.io 프로덕션 패턴은 이 3가지만 확실히 잡으세요
1.Socket.io는 룸·재연결·트랜스포트 폴백 자동 — WebSocket의 표준
2.Redis Adapter로 수평 확장 — 여러 서버 인스턴스 룸 공유
3.인증은 io.use 미들웨어 + handshake.auth.token
공유하기
진행도 34 / 90