# NestJS와 WebSocket 2026: 실시간 통신, Gateway 패턴, 면접 질문 가이드 > NestJS에서 WebSocket 구현을 완벽하게 마스터합니다. Gateway 패턴, Socket.IO 통합, 인증, 에러 핸들링, 2026년 기술 면접 빈출 질문까지 상세히 다룹니다. - Published: 2026-08-10 - Updated: 2026-08-10 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- NestJS WebSocket은 Gateway 패턴을 활용하여 구조화된 실시간 통신 방식을 제공합니다. 원시 WebSocket 구현과 달리 NestJS는 데코레이터를 통해 복잡성을 추상화하고 의존성 주입 시스템과 원활하게 통합됩니다. > **핵심 개념** > > NestJS에서 WebSocket Gateway는 `@WebSocketGateway()` 데코레이터가 적용된 클래스로, 클라이언트와 서버 간의 양방향 통신을 처리합니다. 여러 어댑터(Socket.IO, ws)를 지원하며 NestJS Guards, Pipes, Interceptors와 통합됩니다. ## Socket.IO를 사용한 WebSocket Gateway 설정 NestJS의 기본 어댑터는 [Socket.IO](https://socket.io/docs/v4/)를 사용하며, 자동 재연결, 룸 지원, HTTP 롱 폴링 폴백 기능을 제공합니다. 설치에는 핵심 WebSockets 모듈과 함께 플랫폼별 패키지가 필요합니다. ```bash # terminal npm install @nestjs/websockets @nestjs/platform-socket.io socket.io ``` 기본 Gateway는 수신 메시지를 리스닝하고 연결된 클라이언트에게 응답을 브로드캐스트할 수 있습니다. ```typescript // chat.gateway.ts import { WebSocketGateway, WebSocketServer, SubscribeMessage, MessageBody, ConnectedSocket, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; @WebSocketGateway({ cors: { origin: '*' }, // Configure CORS for browser clients }) export class ChatGateway { @WebSocketServer() server: Server; // Access to the underlying Socket.IO server @SubscribeMessage('message') // Listen for 'message' events handleMessage( @MessageBody() data: { text: string; room: string }, @ConnectedSocket() client: Socket, ): void { // Broadcast to all clients in the room except sender client.to(data.room).emit('message', { text: data.text, senderId: client.id, timestamp: Date.now(), }); } } ``` `@SubscribeMessage` 데코레이터는 메서드를 특정 이벤트 이름에 바인딩합니다. `@MessageBody()`는 페이로드를 추출하고, `@ConnectedSocket()`은 타겟 응답을 위해 클라이언트 소켓에 대한 접근을 제공합니다. ## 연결 관리를 위한 라이프사이클 훅 NestJS Gateway는 클라이언트 연결 및 연결 해제를 처리하기 위한 라이프사이클 인터페이스를 구현합니다. 이러한 훅은 리소스 정리, 접속 상태 추적, 연결 검증을 가능하게 합니다. ```typescript // presence.gateway.ts import { WebSocketGateway, OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; import { Logger } from '@nestjs/common'; @WebSocketGateway() export class PresenceGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { private readonly logger = new Logger(PresenceGateway.name); private connectedUsers = new Map(); afterInit(server: Server): void { // Called once when the gateway initializes this.logger.log('WebSocket Gateway initialized'); } handleConnection(client: Socket): void { // Track new connections this.connectedUsers.set(client.id, { joinedAt: new Date() }); this.logger.log(`Client connected: ${client.id}`); } handleDisconnect(client: Socket): void { // Cleanup on disconnect this.connectedUsers.delete(client.id); this.logger.log(`Client disconnected: ${client.id}`); } } ``` 세 가지 라이프사이클 인터페이스는 각각 다른 목적을 가집니다. `OnGatewayInit`은 시작 시 한 번 실행되고, `OnGatewayConnection`은 클라이언트 연결마다 트리거되며, `OnGatewayDisconnect`는 클라이언트 이탈 시 정리를 처리합니다. ## WebSocket Guards를 통한 인증 WebSocket 연결은 보호된 이벤트에 대한 접근을 허용하기 전에 인증 검증이 필요합니다. NestJS Guards는 Gateway와 함께 작동하지만, 실행 컨텍스트는 HTTP 요청과 다릅니다. 공식 [NestJS WebSockets 문서](https://docs.nestjs.com/websockets/gateways)에서 Guard 통합에 대해 자세히 설명합니다. ```typescript // ws-auth.guard.ts import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { WsException } from '@nestjs/websockets'; import { JwtService } from '@nestjs/jwt'; import { Socket } from 'socket.io'; @Injectable() export class WsAuthGuard implements CanActivate { constructor(private jwtService: JwtService) {} canActivate(context: ExecutionContext): boolean { const client: Socket = context.switchToWs().getClient(); // Extract token from handshake auth or query params const token = client.handshake.auth?.token || client.handshake.query?.token; if (!token) { throw new WsException('Missing authentication token'); } try { const payload = this.jwtService.verify(token as string); // Attach user data to socket for later use client.data.user = payload; return true; } catch { throw new WsException('Invalid token'); } } } ``` `@UseGuards()` 데코레이터를 사용하여 Gateway 클래스 또는 개별 메시지 핸들러에 Guard를 적용합니다. `WsException` 클래스는 클라이언트가 Socket.IO 에러 이벤트를 통해 수신하는 에러를 던집니다. ```typescript // secure-chat.gateway.ts import { UseGuards } from '@nestjs/common'; import { WebSocketGateway, SubscribeMessage } from '@nestjs/websockets'; import { WsAuthGuard } from './ws-auth.guard'; @WebSocketGateway() @UseGuards(WsAuthGuard) // Protect all handlers in this gateway export class SecureChatGateway { @SubscribeMessage('privateMessage') handlePrivateMessage(): void { // Only authenticated clients reach this handler } } ``` 모듈 수준 인증에 관한 질문은 JWT 전략과 세션 관리를 다루는 [NestJS 인증 면접 모듈](/ko/technologies/node-nestjs/interview-questions/authentication-jwt)을 참조하세요. ## 룸 기반 브로드캐스트 패턴 Socket.IO 룸은 연결된 클라이언트의 하위 집합에 대한 타겟 메시지 전달을 가능하게 합니다. 일반적인 사용 사례로는 채팅방, 게임 로비, 라이브 협업 기능이 있습니다. ```typescript // room.gateway.ts import { WebSocketGateway, SubscribeMessage, ConnectedSocket, MessageBody, WebSocketServer, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; @WebSocketGateway() export class RoomGateway { @WebSocketServer() server: Server; @SubscribeMessage('joinRoom') handleJoinRoom( @MessageBody() roomId: string, @ConnectedSocket() client: Socket, ): { event: string; data: string } { // Add client to the specified room client.join(roomId); // Notify others in the room client.to(roomId).emit('userJoined', { userId: client.id }); return { event: 'joinedRoom', data: roomId }; } @SubscribeMessage('leaveRoom') handleLeaveRoom( @MessageBody() roomId: string, @ConnectedSocket() client: Socket, ): void { client.leave(roomId); client.to(roomId).emit('userLeft', { userId: client.id }); } @SubscribeMessage('roomBroadcast') handleRoomBroadcast( @MessageBody() payload: { roomId: string; message: string }, ): void { // Send to all clients in room, including sender this.server.to(payload.roomId).emit('roomMessage', payload.message); } } ``` `client.to(room).emit()`과 `server.to(room).emit()`의 차이점은 중요합니다. 전자는 발신자를 제외하고, 후자는 모든 룸 멤버를 포함합니다. ## 예외 필터를 사용한 에러 핸들링 WebSocket 예외는 HTTP 예외 필터가 Gateway 컨텍스트에 적용되지 않으므로 전용 필터가 필요합니다. 커스텀 필터는 `WsException`을 캐치하고 클라이언트용 에러 응답을 포맷합니다. ```typescript // ws-exception.filter.ts import { Catch, ArgumentsHost, ExceptionFilter } from '@nestjs/common'; import { WsException } from '@nestjs/websockets'; import { Socket } from 'socket.io'; @Catch(WsException) export class WsExceptionFilter implements ExceptionFilter { catch(exception: WsException, host: ArgumentsHost): void { const client: Socket = host.switchToWs().getClient(); const error = exception.getError(); // Send structured error to client client.emit('error', { type: 'WsException', message: typeof error === 'string' ? error : error, timestamp: new Date().toISOString(), }); } } ``` 모든 메시지 핸들러에서 일관된 에러 핸들링을 위해 Gateway 레벨에서 필터를 적용합니다. ```typescript // filtered.gateway.ts import { UseFilters } from '@nestjs/common'; import { WebSocketGateway } from '@nestjs/websockets'; import { WsExceptionFilter } from './ws-exception.filter'; @WebSocketGateway() @UseFilters(WsExceptionFilter) export class FilteredGateway { // All handlers benefit from centralized error handling } ``` 이 패턴은 [미들웨어와 인터셉터 모듈](/ko/technologies/node-nestjs/interview-questions/middleware-interceptors)에서 설명하는 NestJS 데코레이터와 필터 철학을 반영합니다. ## Redis 어댑터를 사용한 스케일링 단일 서버 WebSocket 배포는 로드 밸런싱이 클라이언트를 여러 인스턴스에 분산할 때 실패합니다. [Socket.IO Redis 어댑터](https://socket.io/docs/v4/redis-adapter/)는 pub/sub 메커니즘을 통해 인스턴스 간 이벤트를 동기화합니다. ```typescript // app.module.ts import { Module } from '@nestjs/common'; import { createAdapter } from '@socket.io/redis-adapter'; import { createClient } from 'redis'; import { ChatGateway } from './chat.gateway'; @Module({ providers: [ ChatGateway, { provide: 'REDIS_ADAPTER', useFactory: async () => { const pubClient = createClient({ url: process.env.REDIS_URL }); const subClient = pubClient.duplicate(); await Promise.all([pubClient.connect(), subClient.connect()]); return createAdapter(pubClient, subClient); }, }, ], }) export class AppModule {} ``` Gateway는 인스턴스 간 통신을 위해 어댑터를 사용합니다. ```typescript // scalable.gateway.ts import { WebSocketGateway, WebSocketServer, OnGatewayInit, } from '@nestjs/websockets'; import { Inject } from '@nestjs/common'; import { Server } from 'socket.io'; import { Adapter } from 'socket.io-adapter'; @WebSocketGateway() export class ScalableGateway implements OnGatewayInit { @WebSocketServer() server: Server; constructor(@Inject('REDIS_ADAPTER') private redisAdapter: Adapter) {} afterInit(): void { // Attach Redis adapter for multi-instance support this.server.adapter(this.redisAdapter as any); } } ``` Redis 어댑터를 사용하면 한 서버 인스턴스에서 발행된 메시지가 클러스터 내 모든 인스턴스에 연결된 클라이언트에게 도달합니다. ## NestJS WebSocket 면접 빈출 질문 기술 면접에서는 실시간 아키텍처 결정과 NestJS 특화 구현 세부 사항에 대한 이해를 자주 검증합니다. > **자주 묻는 질문** > > **NestJS에서 WebSocket 인증을 HTTP와 다르게 처리하는 방식은 무엇입니까?** > > HTTP 미들웨어는 WebSocket 연결에 대해 실행되지 않습니다. 인증은 핸드셰이크 단계에서 또는 Gateway에 적용된 Guards를 통해 발생합니다. 토큰 검증은 일반적으로 `handleConnection()`이나 `socket.handshake.auth`에서 읽는 커스텀 Guard에서 수행됩니다. **`@WebSocketGateway()` 포트 설정과 메인 애플리케이션 포트의 차이점은 무엇입니까?** 기본적으로 WebSocket Gateway는 HTTP 서버 포트를 공유합니다. `@WebSocketGateway(3001)`에서 포트를 지정하면 해당 포트에 별도의 WebSocket 서버가 생성됩니다. 공유 포트는 배포를 단순화하지만 리버스 프록시를 사용할 때 경로 기반 라우팅(`/socket.io`)이 필요합니다. **NestJS에서 WebSocket Gateway를 테스트하는 방법은 무엇입니까?** NestJS 테스트 유틸리티는 `Test.createTestingModule()`을 통해 Gateway 테스트를 지원합니다. [socket.io-client](https://github.com/socketio/socket.io-client) 라이브러리가 테스트 서버에 연결합니다. 라이프사이클 훅과 메시지 핸들러는 이벤트를 발행하고 응답이나 부작용을 검증하여 테스트합니다. ```typescript // chat.gateway.spec.ts import { Test } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import { io, Socket } from 'socket.io-client'; import { ChatGateway } from './chat.gateway'; describe('ChatGateway', () => { let app: INestApplication; let client: Socket; beforeAll(async () => { const module = await Test.createTestingModule({ providers: [ChatGateway], }).compile(); app = module.createNestApplication(); await app.listen(3000); client = io('http://localhost:3000'); }); afterAll(async () => { client.disconnect(); await app.close(); }); it('receives message acknowledgment', (done) => { client.emit('message', { text: 'test', room: 'lobby' }); client.on('message', (data) => { expect(data.text).toBe('test'); done(); }); }); }); ``` **Socket.IO 대신 ws 어댑터를 사용해야 하는 경우는 언제입니까?** [ws 라이브러리](https://github.com/websockets/ws) 어댑터(`@nestjs/platform-ws`)는 Socket.IO의 자동 재연결, 룸, 폴백 트랜스포트 같은 기능이 필요하지 않은 애플리케이션에 더 낮은 오버헤드를 제공합니다. 고빈도 트레이딩 시스템과 게임 서버는 레이턴시 감소를 위해 ws를 선호하는 경우가 많습니다. 더 넓은 NestJS 아키텍처 개념에 대해서는 [Guards, Interceptors, 모듈러 아키텍처](/ko/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture) 관련 글에서 이러한 패턴이 HTTP와 WebSocket 컨텍스트 전반에 걸쳐 어떻게 통합되는지 설명합니다. ## 결론 - WebSocket Gateway는 NestJS 의존성 주입, Guards, Filters와 통합되어 HTTP와 실시간 엔드포인트 전반에 걸쳐 일관된 패턴을 제공합니다 - 라이프사이클 훅(`OnGatewayConnection`, `OnGatewayDisconnect`)은 접속 상태 추적과 리소스 정리를 관리합니다 - 인증 흐름은 HTTP와 다르며, 토큰 검증은 핸드셰이크 중 또는 전용 WebSocket Guards를 통해 수행됩니다 - Socket.IO의 룸 기반 브로드캐스팅으로 소켓 목록을 수동으로 관리하지 않고도 타겟 메시징이 가능합니다 - Redis 어댑터는 여러 서버 인스턴스 간 이벤트를 동기화하여 수평 스케일링 문제를 해결합니다 - 예외 필터는 `WsException`과 커스텀 필터를 사용하는 WebSocket 전용 구현이 필요합니다 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ko/blog/node-nestjs/nestjs-websockets-real-time-gateway-best-practices