NestJS와 Redis 연동 채팅 서버 구현
May 07, 2021
1. Redis를 사용하려는 이유
- 백엔드 서버를 이중화하여 운영할 경우, socket 통신에 사용되는 세션 정보를 백엔드 서버 간 공유
- Redis를 Message Queue 서버로 사용
2. Redis와 Socket.io로 채팅 구현
1. 라이브러리 설치
-
install 명령 실행
npm i --save socket.io-redis @nestjs/platform-socket.io
-
트러블 슈팅
-
socket.io-redis
import 시has no call signatures
오류 발생→
socket.io-redis
버전 다운그레이드RedisIoAdapter has no call signatures
→ 버전 업그레이드를 위해서는
adapter
를 직접 구현해야 함Update socket.io to 3.0 in @nestjs/platform-socket.io package · Issue #5676 · nestjs/nest
-
2. Adapter 추가
-
RedisIoAdapter
추가// adapters/redis.adapter.ts import { IoAdapter } from '@nestjs/platform-socket.io'; import * as redisIoAdapter from 'socket.io-redis'; export class RedisIoAdapter extends IoAdapter { createIOServer(port: number): any { const server = super.createIOServer(port); const redisAdapter = redisIoAdapter({ host: 'localhost', port: 6379, }); server.adapter(redisAdapter); return server; } } // main.ts import { NestFactory } from '@nestjs/core'; import { RedisIoAdapter } from './adapters/redis.adapter'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useWebSocketAdapter(new RedisIoAdapter()); await app.listen(3000); } bootstrap();
3. message 이벤트 모듈 추가
-
MessageGateway
추가// message-events/message.gateway.ts import { SubscribeMessage, WebSocketGateway, OnGatewayInit, WebSocketServer, OnGatewayConnection, OnGatewayDisconnect, WsResponse, } from '@nestjs/websockets'; import { Logger } from '@nestjs/common'; import { Socket } from 'socket.io'; import { Server } from 'ws'; @WebSocketGateway({ namespace: '/chat' }) export class MessageGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; private logger: Logger = new Logger('MessageGateway'); public afterInit(server: Server): void { return this.logger.log('Init'); } public handleConnection(client: Socket): void { return this.logger.log(`Client connected: ${client.id}`); } public handleDisconnect(client: Socket): void { return this.logger.log(`Client disconnected: ${client.id}`); } @SubscribeMessage('joinRoom') public joinRoom(client: Socket, room: string): void { client.join(room); client.emit('joinedRoom', room); } @SubscribeMessage('leaveRoom') public leaveRoom(client: Socket, room: string): void { client.leave(room); client.emit('leftRoom', room); } @SubscribeMessage('msgToServer') public handleMessage(client: Socket, payload: any): Promise<WsResponse<any>> { return this.server.to(payload.room).emit('msgToClient', payload); } } // message-events/message.module.ts import { Module } from '@nestjs/common'; import { MessageGateway } from './message.gateway'; @Module({ imports: [], controllers: [], providers: [MessageGateway], }) export class MessageModule {}
4. Docker-compose로 Redis 실행
-
docker-compose.yml
version: '3.9' volumes: redis_data: {} services: redis: image: redis volumes: - redis_data:/var/lib/redis ports: - 6379:6379
5. ConfigModule을 사용해서 RedisIoAdapter 설정하기
-
App Context로부터
ConficService
를 얻어RedisIoAdapter
로 주입// main.ts import { ConfigService } from '@nestjs/config'; import { NestFactory } from '@nestjs/core'; import { RedisIoAdapter } from './adapters/redis.adapter'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); const configService = app.get(ConfigService); app.useWebSocketAdapter(new RedisIoAdapter(app, configService)); await app.listen(configService.get('api.port') ?? 3000); } bootstrap(); // adapters/redis.adapter.ts import { ConfigService } from '@nestjs/config'; import { IoAdapter } from '@nestjs/platform-socket.io'; import * as redisIoAdapter from 'socket.io-redis'; export class RedisIoAdapter extends IoAdapter { constructor(app, private readonly configService: ConfigService) { super(app); } createIOServer(port: number): any { const server = super.createIOServer(port); const redisAdapter = redisIoAdapter({ host: this.configService.get('redis.host'), port: +this.configService.get('redis.port'), }); server.adapter(redisAdapter); return server; } }