채팅서버가 필요한 이유
이번에 라이어게임을 토이프로젝트로 만들어보면서, 생각보다 가장 먼저 필요해진 기능은 게임 로직이 아니라 채팅 서버였다. 플레이어들이 같은 방에 모이고, 서로 의견을 말하고, 실시간으로 반응을 주고받는 구조상 채팅은 필수다. 처음에는 "간단한 토이프로젝트니까 웹소켓 하나 열어서 메시지만 주고받으면 되지 않을까"라고 생각했다. 하지만 실제로 구현을 시작해보니, 익명 사용자 식별, 방 관리, 메시지 흐름 등 고민해야 할 요소들이 하나둘씩 생겨났다. 이 포스트에서는 라이어게임을 만들면서 필요에 의해 구현하게 된 NestJS 기반 채팅 서버에 대해 정리를 하려고 한다.
npm install
npm i @nestjs/websockets @nestjs/platform-socket.io socket.io
https://docs.nestjs.com/websockets/gateways#installation
이번 포스팅도 공식문서를 기반하여 제작을 진행한다.
@nestjs/websockets (NestJS에서 웹소켓을 쓰기 위한 공통 인터페이스)
- @WebSocketGateway() 데코레이터 사용
- @SubscribeMessage()로 이벤트 기반 메시지 처리
- Gateway를 Controller처럼 모듈에 등록
@nestjs/platform-socket.io (Socket.IO를 WebSocket 구현체로 사용)
- Socket.IO 기반의 연결 관리
- Room 기능 (join, leave)
- 브로드캐스트 / 네임스페이스
- 자동 재연결
위의 패키지를 설치하고 nest 에서 제공하는 명령어를 이용하여 게이트웨이를 하나 생성한다.
nest g module chat
nest g ga chat/chat --flat

@WebSocketGateway - (namespace)
@WebSocketGateway(80, { namespace: 'events' })
네임스페이스는 웹소켓 서버 안에서 아예 다른 통신 공간을 나누는 개념이다. 예시로 현재 네임스페이스가 events로 되어 있다면 클라이언트가 서버에 연결시에 아래와 같은 방식으로 연결된다.
- 포트: 80
- 네임스페이스: /events
ws://localhost:80/events
@WebSocketGateway - (transports)
@WebSocketGateway(81, { transports: ['websocket'] })
transports는 Socket.IO가 어떤 방식으로 통신할지 강제로 정하는 옵션이다.
- HTTP polling 사용 안함
- WebSocket만 허용
만약 아무 옵션을 주지 않은 경우에는 내부적으로 아래와 같은 형태로 셋팅된다.
transports: ['polling', 'websocket']
현재 생각한 프로젝트의 간단한 구조
Client
|
| POST /rooms
v
HTTP Controller
|
| createRoom()
v
CreateService
|
| hset room:{id}
| expire 1800
v
Redis
|
| room 상태 저장
|
-----------------------------
|
| socket connect
v
WebSocket Gateway
|
| join(roomId)
v
Socket.IO Room
|
| chat / game events
v
Players
왜 방 생성은 WebSocket이 아니라 HTTP인가?
가장 먼저 떠올릴 수 있는 방법은 WebSocket으로 방 생성을 처리하는 것이다. 하지만 이 방식에는 문제가 있다. WebSocket은 연결 자체가 리소스다. 만약 방 생성을 WebSocket으로 처리한다면, 아직 게임을 시작하지도 않았는데 불필요한 소켓 연결을 유지해야 하는 상황이 발생할 수 있다. 방을 여러 개 생성하는 경우, 의미 없는 연결이 계속 살아 있게 되고 이는 곧 커넥션 수 증가, 메모리 사용량 증가, 비용 문제로 이어질 수 있다. 방 생성은 실시간 통신이 아니다. 이는 서버에 새로운 상태를 만드는 행위이며, 전형적인 리소스 생성 작업이다. 따라서 방 생성은 WebSocket이 아니라 HTTP로 처리하는 것이 역할과 책임 측면에서 더 올바른 선택이다.
왜 RDBMS가 아닌 Redis인가?
라이어게임의 방은 다음과 같은 특징을 가진다. (영속적인 데이터가 아니다)
- 생성되고
- 잠시 사용되다가
- 게임이 끝나면 사라진다
RDBMS를 사용하게 되는 경우에는
- 테이블 설계 필요
- 트랜잭션 관리
- 주기적인 정리 작업 필요
- DELETE 대상 관리
Redis를 사용하게 되는 경우에는
- In-memory 기반이라 접근이 빠름
- TTL 지원하여 자동 삭제
- Hash / Set 구조로 방 상태 표현에 적합
REDIS 설정하기
기본적으로 Redis가 설치되어 있는 서버가 있거나, 로컬에서 Redis를 실행한다는 가정 하에 프로젝트를 진행할 수 있다. 이때 NestJS에서는 Redis와의 연결을 관리하기 위해 RedisService를 별도로 만들어서 사용하는 것이 좋다. 서비스 안에서 Redis 클라이언트를 생성하고, 의존성 주입을 통해 다른 서비스에서 재사용할 수 있도록 설계하면 효율적이다.
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Redis from 'ioredis';
@Injectable()
export class RedisService {
readonly client: Redis;
constructor(private readonly config: ConfigService) {
this.client = new Redis({
host: config.get<string>('REDIS_SERVER'),
port: config.get('REDIS_PORT'),
});
}
}
참고
https://docs.nestjs.com/websockets/gateways
Documentation | NestJS - A progressive Node.js framework
Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea
docs.nestjs.com
'NestJS > 개발' 카테고리의 다른 글
| 실시간 채팅 서버 개발 - 03 (방 접속하기) (1) | 2026.01.19 |
|---|---|
| 실시간 채팅 서버 개발 - 02 (방 생성하기) (0) | 2026.01.18 |
| Redis/BullMQ 이용하여 연산 작업 따로하기 (0) | 2025.12.23 |
| JWT - Passport 사용하기 (0) | 2025.12.22 |
| JWT - 역할 기반 관리하기 (1) | 2025.12.21 |
