
💬 1. 실시간 채팅 예제 구현 (Full Code)
앞서 설정한 ChatGateway 클래스를 확장하여 기본적인 실시간 채팅 기능을 완성해 보겠습니다. 이 예제에서는 클라이언트가 메시지를 전송하면, 서버가 이를 수신하여 발신자를 제외한 모든 연결된 클라이언트에게 메시지를 브로드캐스팅합니다.
// src/chat/chat.gateway.ts
import {
WebSocketGateway,
SubscribeMessage,
MessageBody,
ConnectedSocket,
WsResponse,
OnGatewayConnection,
OnGatewayDisconnect,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { Logger } from '@nestjs/common';
@WebSocketGateway(8080, { namespace: '/chat', cors: { origin: '*' } })
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
private readonly logger = new Logger(ChatGateway.name);
// 서버 인스턴스를 저장하여 브로드캐스팅에 사용 (afterInit에서 초기화)
private io: Server;
afterInit(server: Server) {
this.io = server;
this.logger.log('Chat Gateway 초기화됨');
}
handleConnection(client: Socket) {
this.logger.log(`새 클라이언트 연결: ${client.id}`);
// 연결된 클라이언트들에게 새로운 사용자 접속 알림 (선택 사항)
this.io.emit('userJoined', { id: client.id, message: '새 사용자가 접속했습니다.' });
}
handleDisconnect(client: Socket) {
this.logger.log(`클라이언트 연결 해제: ${client.id}`);
// 연결 해제된 클라이언트들에게 사용자 퇴장 알림 (선택 사항)
this.io.emit('userLeft', { id: client.id, message: '사용자가 나갔습니다.' });
}
// **핵심 채팅 로직**
@SubscribeMessage('sendMessage')
handleMessage(
@MessageBody() data: { username: string; message: string },
@ConnectedSocket() client: Socket,
) {
this.logger.log(`[${data.username}] 메시지 수신: ${data.message}`);
const messagePayload = {
username: data.username,
message: data.message,
timestamp: new Date().toISOString(),
};
// 1. 발신자에게만 응답 (선택 사항)
// client.emit('messageSent', { success: true });
// 2. 발신자를 제외한 모두에게 메시지 브로드캐스팅
client.broadcast.emit('receiveMessage', messagePayload);
// 3. 연결된 모두에게 메시지 브로드캐스팅 (발신자 포함)
// this.io.emit('receiveMessage', messagePayload);
}
}
클라이언트 측 예시 (Socket.io-client):
const socket = io('http://localhost:8080/chat');
// 메시지 전송 socket.emit('sendMessage', { username: 'Alice', message: '안녕하세요!' });
// 메시지 수신 리스너 socket.on('receiveMessage', (payload) => { console.log(${payload.username}: ${payload.message}); });
🔒 2. 인증된 WebSocket 연결 (Authentication)
HTTP 요청과 달리 WebSocket 연결은 지속적이므로, 초기 연결 시 인증 정보를 확인하고 이후 연결 상태를 유지해야 합니다. 가장 일반적인 방법은 연결 시 쿼리 파라미터 또는 헤더를 통해 JWT 토큰을 전달받아 검증하는 것입니다.
1. 연결 시 토큰 전달 및 검증
socket.io를 사용하면, 클라이언트 측에서 연결 요청 시 auth 옵션을 통해 토큰을 전달할 수 있습니다.
클라이언트 (JavaScript/TypeScript):
const token = 'YOUR_JWT_TOKEN_HERE';
const socket = io('http://localhost:8080/chat', {
auth: {
token: token // 토큰을 auth 객체에 담아 전송
}
});
서버 (NestJS Gateway - handleConnection에서 처리):
Gateway에서 handleConnection 메서드를 오버라이드하여 연결 시점에 인증 로직을 구현합니다.
import { WsException } from '@nestjs/websockets';
import * as jwt from 'jsonwebtoken'; // 실제 환경에서는 NestJS Passport 및 JwtService를 사용합니다.
@WebSocketGateway(...)
export class AuthenticatedChatGateway implements OnGatewayConnection {
// ... 기타 설정 생략
handleConnection(client: Socket) {
const token = client.handshake.auth.token; // 1. 클라이언트가 보낸 토큰 추출
if (!token) {
// 2. 토큰이 없으면 연결 거부 및 오류 전송
throw new WsException('인증 토큰이 필요합니다.');
}
try {
// 3. JWT 검증 및 사용자 페이로드 추출
const payload = jwt.verify(token, 'YOUR_SECRET_KEY'); // 실제 비밀키 사용
// 4. 인증 성공! 소켓 객체에 사용자 정보 저장 (이후 메시지 처리 시 사용)
client.data.user = payload;
this.logger.log(`인증 성공 및 연결: ${client.id}, 사용자: ${payload.userId}`);
} catch (e) {
// 5. 토큰이 유효하지 않으면 연결 거부 및 오류 전송
// client.disconnect() 대신 WsException을 사용하여 클라이언트에게 명확한 오류를 보낼 수 있습니다.
throw new WsException('유효하지 않은 토큰입니다.');
}
}
@SubscribeMessage('sendMessage')
handleMessage(@MessageBody() data: string, @ConnectedSocket() client: Socket) {
// 인증된 사용자 정보 사용
const userId = client.data.user.userId;
this.logger.log(`[${userId}]로부터 메시지 수신: ${data}`);
// ... 메시지 처리 로직
}
}
2. NestJS Guard 활용 (고급)
더 견고한 인증을 위해, HTTP 환경에서 사용하는 것처럼 WsGuard를 만들어 @UseGuards() 데코레이터를 Gateway 메서드에 적용할 수도 있습니다. 이는 canActivate 로직을 통해 메시지 이벤트가 처리되기 직전에 인증을 검사할 수 있게 해줍니다.
'Nest.js를 배워보자 > 11. NestJS WebSocket 실전 — 실시간 시스템 만들기' 카테고리의 다른 글
| 🖥️ NestJS 및 NuxtJS를 이용한 실시간 서버 상태 구현 (0) | 2025.12.03 |
|---|---|
| 인증된 WebSocket 연결 (심화) (0) | 2025.12.03 |
| NestJS WebSocket Gateway 기본 설정 (0) | 2025.12.03 |