SKID901's DEVLOG

NestJS와 Redis 연동 채팅 서버 구현

1. Redis를 사용하려는 이유

  • 백엔드 서버를 이중화하여 운영할 경우, socket 통신에 사용되는 세션 정보를 백엔드 서버 간 공유
  • Redis를 Message Queue 서버로 사용

2. Redis와 Socket.io로 채팅 구현

1. 라이브러리 설치

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;
      }
    }
    
    

3. 참고 링크

profile thumbnail
웹 개발자 skid901
개발자로서의 기록 ・ 성장 ・ 공유를 위한 기술 블로그 입니다.
Go back to the homepage