
NestJS를 사용하여 실제 서버의 상태 정보를 실시간으로 수집하고 NuxtJS(클라이언트)로 전송하는 WebSocket Gateway의 구체적이고 실무적인 예제를 보강하여 제공합니다. 이 예제는 시스템 CPU 사용률을 주기적으로 모니터링하고 클라이언트에 푸시합니다.

1. NestJS 프로젝트 설정 및 의존성 설치
Node.js 시스템 정보 수집을 위해 os-utils 라이브러리를 사용합니다.
# NestJS 웹소켓 관련 의존성은 이미 설치되었다고 가정합니다.
# npm install @nestjs/websockets @nestjs/platform-socket.io socket.io
# 서버 상태 모니터링을 위한 유틸리티 설치
npm install os-utils
# 또는
yarn add os-utils
2. 상태 모니터링 서비스 (MonitorService) 구현
실제 서버 상태를 측정하고 관리하는 역할을 담당합니다.
src/monitor/monitor.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import * as os from 'os-utils';
@Injectable()
export class MonitorService implements OnModuleInit {
private currentCpuUsage: number = 0; // CPU 사용률 저장 변수 (0.0 ~ 100.0)
onModuleInit() {
this.startCpuMonitoring();
}
// 1초마다 CPU 사용률을 측정하고 업데이트하는 함수
private startCpuMonitoring() {
setInterval(() => {
os.cpuUsage((v) => {
// os-utils는 CPU 사용률을 0.0부터 1.0 사이의 값으로 반환합니다.
// 클라이언트에게는 백분율(%)로 보여주기 위해 100을 곱합니다.
this.currentCpuUsage = Math.floor(v * 1000) / 10;
});
}, 1000); // 1000ms (1초) 간격으로 측정
}
// 현재 CPU 사용률을 반환하는 메서드
public getCpuUsage(): number {
return this.currentCpuUsage;
}
// 현재 서버의 RAM 사용률을 반환하는 메서드 (추가 정보)
public getMemoryUsage(): { total: number; free: number; used: number; usagePercent: number } {
const total = os.totalmem(); // GB
const free = os.freemem(); // GB
const used = total - free;
const usagePercent = Math.floor((used / total) * 1000) / 10;
return { total, free, used, usagePercent };
}
}
3. WebSocket Gateway (StatusGateway) 구현
MonitorService에서 가져온 데이터를 주기적으로 연결된 모든 클라이언트에게 전송합니다.
src/gateway/status.gateway.ts
import { WebSocketGateway, WebSocketServer, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { Logger } from '@nestjs/common';
import { MonitorService } from '../monitor/monitor.service'; // 상태 모니터링 서비스 주입
// NestJS 서버가 실행 중인 포트와 다른 포트를 사용하거나,
// CORS 설정을 조정해야 할 수 있습니다. (NuxtJS가 기본적으로 3000번을 사용한다고 가정)
@WebSocketGateway(8080, {
cors: {
origin: ['http://localhost:3000', 'http://127.0.0.1:3000'], // NuxtJS 개발 서버 주소
credentials: true
}
})
export class StatusGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server; // Socket.io 서버 인스턴스
private logger: Logger = new Logger('StatusGateway');
private intervalId: NodeJS.Timeout | null = null;
constructor(private readonly monitorService: MonitorService) {} // 서비스 주입
// 1. 게이트웨이 초기화 시 (서버 시작 시)
afterInit(server: Server) {
this.logger.log('WebSocket Gateway Initialized');
// 서버 시작과 동시에 주기적인 상태 전송 시작
this.startStatusBroadcasting();
}
// 2. 클라이언트 연결 시
handleConnection(client: Socket, ...args: any[]) {
this.logger.log(`Client Connected: ${client.id}`);
// 연결된 클라이언트에게 현재 상태를 즉시 전송
this.sendServerStatus(client);
}
// 3. 클라이언트 연결 해제 시
handleDisconnect(client: Socket) {
this.logger.log(`Client Disconnected: ${client.id}`);
// 연결된 클라이언트가 0명이면 브로드캐스팅 중지 (선택 사항)
// if (this.server.engine.clientsCount === 0) {
// this.stopStatusBroadcasting();
// }
}
// **실무 핵심 로직:** 2초마다 모든 클라이언트에게 서버 상태 브로드캐스팅
private startStatusBroadcasting() {
if (this.intervalId) return; // 이미 실행 중이면 중복 실행 방지
this.intervalId = setInterval(() => {
// CPU 및 Memory 상태를 가져옴
const cpuUsage = this.monitorService.getCpuUsage();
const memoryStatus = this.monitorService.getMemoryUsage();
const statusPayload = {
timestamp: new Date().toISOString(),
cpuUsage: cpuUsage, // %
memory: {
total: memoryStatus.total, // GB
used: memoryStatus.used, // GB
usagePercent: memoryStatus.usagePercent // %
},
// 추가적인 서버 상태 항목을 여기에 포함 가능 (예: DB 연결 상태, 요청 수 등)
};
// 연결된 모든 클라이언트에게 'server_status' 이벤트를 통해 상태 전송
this.server.emit('server_status', statusPayload);
}, 2000); // 2000ms (2초) 간격으로 전송
this.logger.log('Status Broadcasting Started (Interval: 2s)');
}
// 특정 클라이언트에게만 상태 전송 (연결 직후 등)
private sendServerStatus(client: Socket) {
const cpuUsage = this.monitorService.getCpuUsage();
const memoryStatus = this.monitorService.getMemoryUsage();
const statusPayload = {
timestamp: new Date().toISOString(),
cpuUsage: cpuUsage,
memory: { total: memoryStatus.total, used: memoryStatus.used, usagePercent: memoryStatus.usagePercent },
};
client.emit('server_status', statusPayload);
}
// 브로드캐스팅 중지 (선택 사항)
// private stopStatusBroadcasting() {
// if (this.intervalId) {
// clearInterval(this.intervalId);
// this.intervalId = null;
// this.logger.log('Status Broadcasting Stopped');
// }
// }
}
4. 모듈 구성
NestJS 앱 모듈에 서비스와 게이트웨이를 등록해야 합니다.
src/app.module.ts
import { Module } from '@nestjs/common';
import { MonitorService } from './monitor/monitor.service';
import { StatusGateway } from './gateway/status.gateway';
@Module({
imports: [],
controllers: [],
providers: [
MonitorService, // 서버 상태 모니터링 서비스
StatusGateway, // WebSocket 게이트웨이
],
})
export class AppModule {}
결론
이 예제는 MonitorService에서 서버 상태(CPU, Memory)를 지속적으로 측정하고, StatusGateway가 이 데이터를 받아 setInterval 타이머를 이용해 2초마다 연결된 모든 NuxtJS 클라이언트에게 server_status라는 이벤트로 실시간 푸시하는 실무적인 NestJS 백엔드 구현 방식을 보여줍니다.
클라이언트(NuxtJS)는 socket.on('server_status', ...)를 통해 이 데이터를 수신하고 UI를 갱신하게 됩니다.
🌐 NuxtJS (클라이언트) 구현: 실시간 서버 상태 수신
NuxtJS 프로젝트에서 앞서 NestJS가 포트 8080으로 송신하는 server_status 이벤트를 수신하고 화면에 표시하는 클라이언트 로직입니다.
1. 의존성 설치
클라이언트 측에서 WebSocket 서버에 연결하기 위해 socket.io-client를 설치합니다.
npm install socket.io-client
# 또는
yarn add socket.io-client
2. 상태 수신 및 표시 컴포넌트 구현
Nuxt 3 환경에서 Vue의 Composition API를 사용하여 컴포넌트의 상태를 관리하고, 서버에 연결 및 데이터를 수신합니다.
pages/index.vue (메인 페이지)
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { io } from 'socket.io-client';
// NestJS WebSocket 서버 주소 (게이트웨이 포트 8080)
const NESTJS_WS_URL = 'http://localhost:8080';
// 실시간 상태를 저장할 반응형 변수
const serverStatus = ref(null);
const connectionStatus = ref('연결 중...');
let socket = null;
onMounted(() => {
// 1. Socket.io 서버에 연결
socket = io(NESTJS_WS_URL);
// 2. 연결 성공 이벤트 처리
socket.on('connect', () => {
connectionStatus.value = '🟢 연결 성공';
console.log('Socket connected successfully!');
});
// 3. 연결 실패 이벤트 처리
socket.on('disconnect', () => {
connectionStatus.value = '🔴 연결 끊김';
console.log('Socket disconnected.');
});
// 4. 서버로부터 'server_status' 이벤트 수신
// NestJS StatusGateway에서 2초마다 전송하는 데이터 수신
socket.on('server_status', (data) => {
console.log('실시간 서버 상태 수신:', data);
serverStatus.value = data; // 수신된 데이터를 반응형 상태에 저장
});
// 5. 연결 오류 처리 (선택 사항)
socket.on('connect_error', (err) => {
connectionStatus.value = `🔴 연결 오류: ${err.message}`;
console.error('Connection Error:', err);
});
});
// 컴포넌트가 파괴될 때 (페이지 이동 등) 소켓 연결 해제
onUnmounted(() => {
if (socket) {
socket.disconnect();
}
});
// UI 표시를 위한 시간 포맷팅 함수
const formatTime = (isoString) => {
if (!isoString) return 'N/A';
return new Date(isoString).toLocaleTimeString('ko-KR');
};
</script>
<template>
<div class="container">
<h1>🚀 실시간 NestJS 서버 상태 모니터링</h1>
<p><strong>WebSocket 상태:</strong> <span :style="{ color: connectionStatus.startsWith('🟢') ? 'green' : 'red' }">{{ connectionStatus }}</span></p>
<div v-if="serverStatus" class="status-card">
<p class="timestamp">최종 수신 시간: {{ formatTime(serverStatus.timestamp) }}</p>
<h2>💻 CPU 사용률</h2>
<div class="status-item">
<p class="value">{{ serverStatus.cpuUsage.toFixed(1) }} %</p>
<div class="bar-container">
<div class="bar" :style="{ width: serverStatus.cpuUsage + '%' }"></div>
</div>
</div>
<h2>🧠 메모리 사용률</h2>
<div class="status-item">
<p class="value">{{ serverStatus.memory.usagePercent.toFixed(1) }} %</p>
<div class="bar-container">
<div class="bar" :style="{ width: serverStatus.memory.usagePercent + '%' }"></div>
</div>
<p class="detail">사용 중: {{ serverStatus.memory.used.toFixed(2) }} GB / 전체: {{ serverStatus.memory.total.toFixed(2) }} GB</p>
</div>
</div>
<div v-else class="loading">
<p>서버 상태 데이터 수신 대기 중...</p>
</div>
</div>
</template>
<style scoped>
.container {
max-width: 600px;
margin: 50px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
h1 { color: #3498db; }
h2 { font-size: 1.2em; border-bottom: 1px solid #eee; padding-bottom: 5px; margin-top: 20px;}
.status-card {
margin-top: 20px;
padding: 15px;
background: #f9f9f9;
border-radius: 6px;
}
.timestamp {
font-size: 0.8em;
color: #777;
text-align: right;
}
.status-item {
margin-bottom: 15px;
}
.value {
font-size: 1.5em;
font-weight: bold;
color: #2c3e50;
margin: 5px 0;
}
.detail {
font-size: 0.9em;
color: #555;
margin-top: 5px;
}
.bar-container {
height: 10px;
background-color: #eee;
border-radius: 5px;
overflow: hidden;
}
.bar {
height: 100%;
background-color: #e74c3c; /* 빨간색으로 상태 표시 */
transition: width 0.3s ease;
}
</style>
3. 주요 로직 요약
- 연결 URL: io('http://localhost:8080')를 사용하여 NestJS의 StatusGateway가 열려 있는 포트 8080에 접속합니다.
- 반응형 상태: serverStatus = ref(null)을 사용하여 수신된 데이터를 저장하고, 이 변수가 업데이트되면 NuxtJS/Vue 템플릿이 자동으로 갱신됩니다.
- 이벤트 수신: socket.on('server_status', (data) => { ... })를 통해 백엔드에서 브로드캐스트된 server_status 이벤트를 포착하고 그 안에 담긴 JSON 데이터를 serverStatus에 저장합니다.
- 라이프사이클 관리: onMounted에서 연결을 시작하고, onUnmounted에서 socket.disconnect()를 호출하여 페이지를 벗어날 때 연결을 깔끔하게 해제합니다.
이로써 NestJS와 NuxtJS 간의 실시간 WebSocket 통신을 통한 서버 상태 모니터링 시스템의 전체 구현이 완료됩니다.
이 영상은 NestJS에서 WebSocket Gateway를 생성하고 사용하는 기본 방법을 설명하여 백엔드 구현에 도움이 될 수 있습니다.
NestJS Websockets Tutorial #1 - Creating a Websocket Gateway Server
🛡️ NestJS/NuxtJS 실시간 시스템의 확장성 및 인증 처리
네, 실시간 애플리케이션을 실제 서비스에 배포할 때는 확장성과 인증 문제가 필수적으로 고려되어야 합니다. 특히 WebSocket은 HTTP와 달리 한 번 연결되면 장시간 유지되므로 세션 관리가 중요합니다.
1. 🚀 확장성 (Scaling) 처리
NestJS 서버를 여러 인스턴스로 확장(Scale-out)할 때, WebSocket 연결을 관리하는 방법에 대한 접근 방식입니다.
문제:
일반적으로 클라이언트는 로드 밸런서를 통해 특정 NestJS 인스턴스(Server A)에 연결됩니다. 만약 Server A에 연결된 클라이언트가 Server B에서 발생한 상태 변화 데이터를 받아야 한다면, 이 두 서버 인스턴스는 서로 통신할 방법이 필요합니다.
해결책: 어댑터 (Adapter) 사용
Socket.io는 이 문제를 해결하기 위해 Adapter 개념을 제공합니다. 가장 일반적인 방법은 Redis를 메시지 브로커로 사용하는 것입니다.
- Redis Adapter 설치 (NestJS 백엔드):
npm install @socket.io/redis-adapter ioredis - 작동 방식:
- 모든 NestJS 서버 인스턴스(A, B, C...)는 동일한 Redis Pub/Sub 채널에 연결됩니다.
- Server B에서 상태 업데이트가 발생하여 this.server.emit('event', data)를 호출하면, Redis Adapter는 이 이벤트를 Redis의 특정 채널에 발행(Publish)합니다.
- 다른 모든 인스턴스(A, C...)는 Redis 채널을 구독(Subscribe)하고 있다가 이 메시지를 수신합니다.
- 수신한 메시지를 통해 자신에게 연결된 클라이언트들에게 이벤트를 푸시(Push)합니다.
이 방식을 사용하면, 수많은 클라이언트가 여러 NestJS 인스턴스에 분산 연결되어 있어도 모든 클라이언트가 실시간 데이터를 일관되게 수신할 수 있습니다.
2. 🔑 인증 (Authentication) 및 권한 부여 (Authorization) 처리
WebSocket 연결 시 클라이언트가 유효한 사용자인지 확인하고, 해당 사용자가 특정 데이터를 수신할 권한이 있는지 확인해야 합니다.
1. Handshake 단계에서의 인증 (JWT 사용)
WebSocket 연결이 설정되는 초기 단계(Handshake)에서 사용자를 인증합니다.
- NuxtJS 클라이언트: 클라이언트는 HTTP 로그인 후 받은 JWT(JSON Web Token)를 WebSocket 연결 요청 시 서버에 전달해야 합니다.
// NuxtJS (Client) const token = localStorage.getItem('access_token'); const socket = io(NESTJS_WS_URL, { auth: { token: token // 토큰을 Handshake 데이터에 포함하여 전송 } }); - NestJS Gateway: NestJS의 StatusGateway에서 Socket.io의 미들웨어 기능을 사용하여 연결 전에 토큰을 검증합니다.
// NestJS Gateway (status.gateway.ts) - 일부 수정 afterInit(server: Server) { // 서버 인스턴스에 미들웨어를 적용 server.use(async (socket: Socket, next) => { const token = socket.handshake.auth.token; if (!token) { return next(new Error('인증 토큰 누락')); } try { // 1. 토큰 검증 로직 (예: JWT Service를 사용하여 검증) const payload = await this.jwtService.verifyAsync(token); // 2. 소켓 인스턴스에 사용자 정보 저장 socket['user'] = payload; next(); // 인증 성공 -> 연결 허용 } catch (e) { next(new Error('유효하지 않은 토큰')); // 인증 실패 -> 연결 거부 } }); // ... 기존 초기화 로직 ... }
2. 권한 부여 (Authorization)
인증된 사용자에게만 특정 이벤트를 보내야 할 경우, NestJS Gateway에서 소켓 인스턴스에 저장된 사용자 정보(socket['user'])를 사용합니다.
- 특정 방(Room) 사용: 사용자 인증 후, 해당 사용자의 ID를 기반으로 Room에 가입시킵니다.
// NestJS Gateway - handleConnection 내 handleConnection(client: Socket) { const userId = client['user'].sub; // 인증 미들웨어에서 저장된 user 정보 client.join(`user-room-${userId}`); // 사용자 ID 기반의 방에 가입 this.logger.log(`Client ${client.id} joined room user-room-${userId}`); } // 특정 사용자에게만 메시지 전송 sendNotificationToUser(targetUserId: number, message: string) { this.server.to(`user-room-${targetUserId}`).emit('private_notification', message); }
이 방식을 통해 수백만 개의 소켓 연결이 분산된 상황에서도 특정 사용자에게만 알림을 보내거나, 권한이 있는 사용자에게만 서버 상태 데이터를 제공할 수 있게 됩니다.
'Nest.js를 배워보자 > 11. NestJS WebSocket 실전 — 실시간 시스템 만들기' 카테고리의 다른 글
| 인증된 WebSocket 연결 (심화) (0) | 2025.12.03 |
|---|---|
| 실시간 채팅 예제와 인증된 WebSocket 연결 (0) | 2025.12.03 |
| NestJS WebSocket Gateway 기본 설정 (0) | 2025.12.03 |