https://stack501.tistory.com/71
Socket.io - 정의, 설치, 작동 원리, 특징
1. Socket.io 란?Socket.IO는 웹 소켓 연결을 통해 클라이언트와 서버간에 실시간 양방향 통신을 가능하게하는 JavaScript 라이브러리이다.실시간 양방향 통신이기 때문에 채팅방, 실시간 게임 등에 사용
stack501.tistory.com
https://stack501.tistory.com/72
Socket.io - 통신 방법, 네임스페이스, Room, broadcast, private
1. 통신 방법1.1. socket.io 받아오기const SocketIO = require('socket.io');const io = SocketIO(server, { path: '/socket.io' });서버에서는 위와 같이 설치된 socket.io를 require하여 받아와 사용할 수 있다.1.2. 기본적인 송신
stack501.tistory.com
https://stack501.tistory.com/73
Socket.io - 실전 예제
1. socket 인스턴스 생성 const io = SocketIO(server, { path: '/socket.io' }); 2. namespace 생성const room = io.of('/room');const chat = io.of('/chat');3. room 네임스페이스 접속room.on('connection', (socket) => { console.log('room 네임스
stack501.tistory.com
Node.js에서 Socket.io에 대해서 배워본 적이 있다.
NestJS에서는 어떻게 구조화되었는지 살펴보고 코드를 작성해보자.
1. 패키지 설치
yarn add @nestjs/websockets @nestjs/platform-socket.io socket.io
// 또는
npm i @nestjs/websockets @nestjs/platform-socket.io socket.io
2. Gateway 생성하고 메세지 리스닝하기
2.1. 리소스 생성
2.2. ChatsGateway 구현
import { MessageBody, OnGatewayConnection, SubscribeMessage, WebSocketGateway } from "@nestjs/websockets";
import { Socket } from "socket.io";
@WebSocketGateway({
// ws://localhost:3000/chats
namespace: 'chats'
})
export class ChatsGateway implements OnGatewayConnection {
handleConnection(socket: Socket) {
console.log(`on connect called : ${socket.id}`);
}
// socket.on('send_message', (message) => { console.log(message) });
@SubscribeMessage('send_message')
sendMessage(
@MessageBody() message: string,
) {
console.log(message);
}
}
기존 express socket과 다른 부분은 클래스로 관리가 가능하다는 점이다.
@WebSocketGateway 데코레이터에 포트나 네임스페이스를 정의 가능하며, OnGatewayConnection을 통해 handleConnection 등과 같은 기본적인 연결 및 연결해제에 대한 설정, @SubscribeMessage를 통해 이벤트와 메세지를 받을 수 있는 메서드 작성 등을 할 수 있다.
2.3. chats.module.ts 에 provider 로 등록
import { Module } from '@nestjs/common';
import { ChatsService } from './chats.service';
import { ChatsController } from './chats.controller';
import { ChatsGateway } from './chats.gateway';
@Module({
controllers: [ChatsController],
providers: [ChatsGateway, ChatsService],
})
export class ChatsModule {}
ChatsGateway를 사용하기 위해선 ChatsGateway를 providers에 등록하여야 한다.
2.4. Postman에서 테스트
2.4.1. Postman 연결
Postman에서 Socket.IO에 대한 것을 테스트해볼 수 있다. new를 눌러 새로운 Collection으로 생성한다.
구현한 namespace를 host 뒤에 붙여 연결이 가능하다.
연결이 되면 Connect 버튼이 Disconnect로 변경되며, 콘솔에 연결이 잘 되었다고 표시된다.
console.log(`on connect called : ${socket.id}`);
또한 서버에서 연결된 ID를 볼 수 있다. 이 부분은 소켓이 연결될 때 우리가 코드를 작성한 부분이 출력된 것이다.
2.4.2. 메세지 보내기
Message에 서버로 보낼 메세지를 작성하고,
오른쪽 아래 부분에 이벤트를 입력하고 Send 버튼을 누르면 메세지를 보내게 된다.
성공하면 로그가 위와 같이 뜨는 것을 볼 수 있다.
서버에서도 메세지가 잘 들어온다.
3. 서버에서 이벤트 전송하기
반대로 emit을 통해 서버에서 클라이언트로 이벤트를 전송하도록 할 수 있다.
3.1. 로직 수정
import { MessageBody, OnGatewayConnection, SubscribeMessage, WebSocketGateway, WebSocketServer } from "@nestjs/websockets";
import { Server, Socket } from "socket.io";
@WebSocketGateway({
// ws://localhost:3000/chats
namespace: 'chats'
})
export class ChatsGateway implements OnGatewayConnection {
@WebSocketServer()
server: Server;
handleConnection(socket: Socket) {
console.log(`on connect called : ${socket.id}`);
}
// socket.on('send_message', (message) => { console.log(message) });
@SubscribeMessage('send_message')
sendMessage(
@MessageBody() message: string,
) {
console.log(message);
this.server.emit('receive_message', 'hello from server');
}
}
@WebSocketServer()
server: Server;
server는 @WebSocketServer() 데코레이터와 socket.io의 Server 클래스로 구현되는 프로퍼티이다. server는 io.on 에서 (socket) => 의 socket과 동일한 역할을 한다고 볼 수 있다.
this.server.emit('receive_message', 'hello from server');
io.on 내에서 socket.emit을 할 수 있듯, server.emit으로 클라이언트로 통신할 수 있다.
3.2. Postman 설정
서버에서 클라이언트로 보내는 emit은 Postman에서 Events 탭에서 설정할 수 있다.
여러 개의 클라이언트를 구성해서 Events 탭에만 구성한다.
User 1에서 Send를 하게 되면
User 1, User 2, User 3 모두에게 receive_message 이벤트와 메세지가 오게 된다.
4. Room 활용하기
4.1. 로직 수정하기
import { ConnectedSocket, MessageBody, OnGatewayConnection, SubscribeMessage, WebSocketGateway, WebSocketServer } from "@nestjs/websockets";
import { Server, Socket } from "socket.io";
@WebSocketGateway({
// ws://localhost:3000/chats
namespace: 'chats'
})
export class ChatsGateway implements OnGatewayConnection {
@WebSocketServer()
server: Server;
handleConnection(socket: Socket) {
console.log(`on connect called : ${socket.id}`);
}
@SubscribeMessage('enter_chat')
async enterChat(
// 방의 chat ID들을 리스트로 받는다.
@MessageBody() data: number[],
@ConnectedSocket() socket: Socket,
) {
for(const chatId of data) {
await socket.join(chatId.toString());
}
}
// socket.on('send_message', (message) => { console.log(message) });
@SubscribeMessage('send_message')
sendMessage(
@MessageBody() message: {message: string, chatId: number},
@ConnectedSocket() socket: Socket,
) {
console.log(message);
this.server.in(message.chatId.toString()).emit('receive_message', message.message);
}
}
@SubscribeMessage('enter_chat')
async enterChat(
// 방의 chat ID들을 리스트로 받는다.
@MessageBody() data: number[],
@ConnectedSocket() socket: Socket,
) {
for(const chatId of data) {
await socket.join(chatId.toString());
}
}
enter_chat 이벤트와 배열에 number타입의 id를 넣으면 id에 해당하는 Room으로 접속하게 된다.
@SubscribeMessage('send_message')
sendMessage(
@MessageBody() message: {message: string, chatId: number},
@ConnectedSocket() socket: Socket,
) {
console.log(message);
this.server.in(message.chatId.toString()).emit('receive_message', message.message);
}
server.in(Room Id) 은 해당 Room에 접속한 소켓들에게 메세지를 보내게 된다.
4.2. Postman 으로 설정하고 테스트해보기
User 1에 Message를 [1]로 설정하고 enter_chat 이벤트로 설정한 후 Send를 보내 1번방에 접속하도록 한다.
User 2는 Message를 [2]로 설정하고 enter_chat 이벤트로 설정한 후 Send를 보내 2번방에 접속하도록 한다.
User 3은 Message를 [1]로 설정하고 enter_chat 이벤트로 설정한 후 Send를 보내 1번방에 접속하도록 한다.
다시 User 1로 돌아와서 Text를 JSON으로 변경한다.
User 1에서 Message를 작성 후 send_message 이벤트로 설정 후 Send 버튼을 눌러 1번 방에 있는 소켓들에게 메세지를 보낸다.
2번 방에 있는 User 2는 메세지가 오지 않는다.
1번 방에 있는 User 3은 메세지가 온 것을 볼 수 있다.
이렇게 Room을 설정할 수 있다.
5. Broadcasting
5.1. 로직 수정하기
@SubscribeMessage('send_message')
sendMessage(
@MessageBody() message: {message: string, chatId: number},
@ConnectedSocket() socket: Socket,
) {
console.log(message);
// broadcast
socket.to(message.chatId.toString()).emit('receive_message', message.message);
// room 통신
// this.server.in(message.chatId.toString()).emit('receive_message', message.message);
}
socket.to 를 통해 특정 방에만 소속되어있는 소켓들에게 통신하지만 본인을 제외하는 브로드캐스트 통신을 할 수 있다.
5.2. Postman에서 확인하기
모든 User들을 접속시키고 User 1에서 통신을 했을 때, User 1의 로그에선 send_message만 존재한다.
User 2는 2번 방에 속해있으므로 메세지를 받지 않는다.
User 3만이 receive_message를 받게 된다.
6. Chat Entity 생성하기
Chat을 좀 더 체계적으로 관리하기 위해 Entity로 만들어 구현해보도록 하자.
6.1. CreateChatDto 생성
import { IsNumber } from "class-validator";
export class CreateChatDto {
@IsNumber({}, {each: true})
userIds: number[];
}
우선 ChatsModel에 사용할 DTO를 위와 같이 구현한다.
6.2. ChatsModel 생성과 UsersModel 수정
import { BaseModel } from "src/common/entity/base.entity";
import { UsersModel } from "src/users/entities/users.entity";
import { Entity, ManyToMany } from "typeorm";
@Entity()
export class ChatsModel extends BaseModel {
@ManyToMany(() => UsersModel, (user) => user.chats)
users: UsersModel[];
}
ChatsModel은 다대다 관계로 UsersModel과 연결한다.
@ManyToMany(() => ChatsModel, (chat) => chat.users)
@JoinTable()
chats: ChatsModel[];
마찬가지로 UsersModel에서 ChatsModel을 연결한다.
6.3. app.module.ts 의 entities에 ChatsModel 등록
TypeOrmModule.forRoot({
type: 'postgres',
host: process.env[ENV_DB_HOST_KEY],
port: parseInt(process.env[ENV_DB_PORT_KEY]!),
username: process.env[ENV_DB_USERNAME_KEY],
password: process.env[ENV_DB_PASSWORD_KEY],
database: process.env[ENV_DB_DATABASE_KEY],
entities: [
PostsModel,
UsersModel,
ImageModel,
ChatsModel,
],
synchronize: true, //프로덕션 환경에선 false해야함
}),
ChatsModel을 TypeORM에서 사용하기 위해선 app.module.ts의 entities에 모델을 등록하여야 한다.
6.4. chats.module.ts 에 ChatsModel을 Import
import { Module } from '@nestjs/common';
import { ChatsService } from './chats.service';
import { ChatsController } from './chats.controller';
import { ChatsGateway } from './chats.gateway';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ChatsModel } from './entity/chats.entity';
@Module({
imports: [
TypeOrmModule.forFeature([ChatsModel]),
],
controllers: [ChatsController],
providers: [ChatsGateway, ChatsService],
})
export class ChatsModule {}
또한 chats.module.ts 에도 ChatsModel을 사용하도록 imports에 등록하여야 한다.
6.5. ChatsService 구현
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ChatsModel } from './entity/chats.entity';
import { Repository } from 'typeorm';
import { CreateChatDto } from './dto/create-chat.dto';
@Injectable()
export class ChatsService {
constructor(
@InjectRepository(ChatsModel)
private readonly chatsRepository: Repository<ChatsModel>,
){}
async createChat(dto: CreateChatDto) {
const chat = await this.chatsRepository.save({
users: dto.userIds.map((id) => ({id})),
});
return this.chatsRepository.findOne({
where: {
id: chat.id,
}
});
}
}
코어로직을 작성하기 위해 ChatsService를 생성하고, createChat이라는 메서드로 구현하였다.
6.6. chats.gateway.ts 의 createChat 메서드 수정
constructor(
private readonly chatsService: ChatsService,
) {}
@SubscribeMessage('create_chat')
async createChat(
@MessageBody() data: CreateChatDto,
@ConnectedSocket() socket: Socket,
) {
const chat = await this.chatsService.createChat(
data,
);
}
chats.gateway.ts 의 createChat에서 ChatsService의 createChat을 불러와 data를 넣어준다. 참고로 chatsService를 사용하기 위해선 생성자에 주입하여야 한다.
6.7. Postman 에서 create_chat 실행
create_chat을 구현하였으니, Postman에서 연결 후 create_chat을 실행할 수 있다.
6.8. pgAdmin에서 컬럼 확인하기
데이터베이스에 모델들과 컬럼이 생긴 것을 볼 수 있다.
7. Paginate Chat API 생성하기
좀 더 고도화하기 위해 Pagination을 사용해보도록 하자.
7.1. PaginateChatDto 구현하기
import { BasePaginationDto } from "src/common/dto/base-pagination.dto";
export class PaginateChatDto extends BasePaginationDto {
}
위와 같이 PaginateChatDto를 구현한다.
7.2. chats.module.ts에 CommonService를 import하기
import { Module } from '@nestjs/common';
import { ChatsService } from './chats.service';
import { ChatsController } from './chats.controller';
import { ChatsGateway } from './chats.gateway';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ChatsModel } from './entity/chats.entity';
import { CommonModule } from 'src/common/common.module';
@Module({
imports: [
TypeOrmModule.forFeature([ChatsModel]),
CommonModule,
],
controllers: [ChatsController],
providers: [ChatsGateway, ChatsService],
})
export class ChatsModule {}
CommonService를 사용하기 위해선 chats.module.ts에 CommonModule을 imports 하여야 한다.
7.3. chats.service.ts에 paginateChats 메서드 구현하기
constructor(
@InjectRepository(ChatsModel)
private readonly chatsRepository: Repository<ChatsModel>,
private readonly commonService: CommonService,
){}
paginateChats(dto: PaginateChatDto) {
return this.commonService.paginate(dto,
this.chatsRepository,
{
},
'chats',
)
}
생성자에서 CommonService를 불러오고, paginateChats를 구현한다. paginateChats는 채팅을 Pagination하게 가져오는 메서드일뿐이다.
7.3. chats.controller.ts에 paginateChat 메서드 구현하기
import { Controller, Get, Query } from '@nestjs/common';
import { ChatsService } from './chats.service';
import { PaginateChatDto } from './dto/paginate-chat.dto';
@Controller('chats')
export class ChatsController {
constructor(private readonly chatsService: ChatsService) {}
@Get()
paginateChat(
@Query() dto: PaginateChatDto,
) {
return this.chatsService.paginateChats(dto);
}
}
Service에 구현한 paginateChats를 사용하기 위해 컨트롤러에 구현한다.
7.4. Postman에서 API 생성하기
Postman에서 chats API를 생성한다. paginateChat은 특별히 경로를 지정하지 않았기 때문에, /chats로 설정하면 된다.
Send를 보내면 생성한 Chat들이 보이게 된다.
7.5. chat에 user가 보이도록 설정하기
chat에 user가 보이도록 하는 방법은 두 가지가 있다.
7.5.1. ChatsModel에 eager 설정하기
import { BaseModel } from "src/common/entity/base.entity";
import { UsersModel } from "src/users/entities/users.entity";
import { Entity, ManyToMany } from "typeorm";
@Entity()
export class ChatsModel extends BaseModel {
@ManyToMany(() => UsersModel, (user) => user.chats, {
eager: true,
})
users: UsersModel[];
}
ChatsModel에 eager를 true로 활성화한다.
7.5.2. chats.service.ts 의 paginateChats의 옵션 설정
paginateChats(dto: PaginateChatDto) {
return this.commonService.paginate(dto,
this.chatsRepository,
{
relations: {
users: true,
}
},
'chats',
)
}
relations 에 users를 true로 활성화한다.
8. Enter Chat 이벤트 업데이트 & WSException 던지기
8.1. EnterChatDto 구현하기
import { IsNumber } from "class-validator";
export class EnterChatDto {
@IsNumber({}, {each: true})
chatIds: number[];
}
enterChat의 @MessageBody() 부분을 개선하기 위해 EnterChatDto를 구현한다.
8.2 Chat이 있는지 체크하는 메서드 구현하기
async checkIfChatExists(chatId: number) {
const exists = this.chatsRepository.exists({
where: {
id: chatId,
}
});
return exists;
}
enterChat에는 Chat이 있는지 검사를 따로 하지않기 때문에, chatId에 따른 Chat이 존재하는지 검사하는 메서드를 Service에 작성한다.
8.3. enterChat 메서드 수정하기
@SubscribeMessage('enter_chat')
async enterChat(
// 방의 chat ID들을 리스트로 받는다.
@MessageBody() data: EnterChatDto,
@ConnectedSocket() socket: Socket,
) {
for(const chatId of data.chatIds) {
const exists = await this.chatsService.checkIfChatExists(chatId);
if(!exists) {
throw new WsException({
code: 100,
message: `존재하지 않는 chat 입니다. chatId: ${chatId}`,
});
}
}
await socket.join(data.chatIds.map((x) => x.toString()));
}
enterChat의 data부분을 EnterChatDto로 수정하고, checkIfChatExists 메서드로 Chat이 존재하는지 체크한다.
만약 Chat이 없다면 WsException 을 날려 클라이언트에 에러를 알려준다.
8.3 WsErrorFilter 구현하기
import { Catch, ArgumentsHost } from '@nestjs/common';
import { WsException, BaseWsExceptionFilter } from '@nestjs/websockets';
@Catch(WsException)
export class WsErrorFilter extends BaseWsExceptionFilter {
catch(exception: WsException, host: ArgumentsHost) {
// WebSocket 컨텍스트에서 클라이언트와 데이터를 가져옴
const wsContext = host.switchToWs();
const client = wsContext.getClient();
// 예외에서 에러 메시지를 추출 (예: "Invalid data")
const error = exception.getError();
const errorMessage = typeof error === 'string'
? error
: (error && (error as any).message ? (error as any).message : 'Unknown error');
// Socket.IO를 사용하는 경우, 'exception' 이벤트로 클라이언트에게 오류 메시지 전달
client.emit('exception', { status: 'error', message: errorMessage });
}
}
WsException을 잡기 위해선 따로 Exception Filter를 구현해야 한다.
@WebSocketGateway({
// ws://localhost:3000/chats
namespace: 'chats'
})
@UseFilters(new WsErrorFilter())
export class ChatsGateway implements OnGatewayConnection {
구현된 필터를 ChatsGateway에 적용하면, 전체에 필터를 적용할 수 있다. (혹은 메서드 단위로 적용도 가능하고, 이전에 설명했던 것처럼 main.ts에 적용하면 모든 Websocket에 필터를 걸 수 있다.)
8.4. Postman에서 확인하기
우선 Events에 exception 이벤트를 활성화한다.
만약 없는 chatIds 를 넣어 Send를 하면 로그에 exception이 오는 것을 볼 수 있다.
9. 메세지 보내기 마무리하기
이전에는 채팅방(Chat)에 대한 것을 구현했다면, 이번엔 메세지를 모델로 만들고 Chat과 User와의 관계를 맺어 구현해보도록 하자.
9.1. MessagesModel 만들기
import { IsString } from "class-validator";
import { ChatsModel } from "src/chats/entity/chats.entity";
import { BaseModel } from "src/common/entity/base.entity";
import { UsersModel } from "src/users/entities/users.entity";
import { Column, Entity, ManyToOne } from "typeorm";
@Entity()
export class MessagesModel extends BaseModel {
@ManyToOne(() => ChatsModel, (chat) => chat.messages)
chat: ChatsModel;
@ManyToOne(() => UsersModel, (user) => user.messages)
author: UsersModel;
@Column()
@IsString()
message: string;
}
MessagesModel은 하나의 채팅방에서 여러 개의 메세지를 가질 수 있으니 ManyToOne으로 ChatsModel과 관계를 맺고, 한명의 사용자가 여러 개의 메세지를 보낼 수 있으니 마찬가지로 UsersModel과 ManyToOne 관계를 맺는다.
그리고 MessagesModel DB에 저장될 메세지(string) 필드도 추가한다.
import { BaseModel } from "src/common/entity/base.entity";
import { UsersModel } from "src/users/entities/users.entity";
import { Entity, ManyToMany, OneToMany } from "typeorm";
import { MessagesModel } from "../messages/entity/messages.entity";
@Entity()
export class ChatsModel extends BaseModel {
@ManyToMany(() => UsersModel, (user) => user.chats)
users: UsersModel[];
@OneToMany(() => MessagesModel, (message) => message.chat)
messages: MessagesModel[];
}
ChatsModel에서도 MessagesModel과 관계를 맺어준다.
@OneToMany(() => MessagesModel, (message) => message.author)
messages: MessagesModel[];
UsersModel에서도 마찬가지로 관계를 맺어준다.
9.2. 모듈에 등록하기
9.2.1. app.module.ts 의 TypeOrmModule.forRoot 내의 entities에 등록하기
entities: [
PostsModel,
UsersModel,
ImageModel,
ChatsModel,
MessagesModel,
],
MessagesModel을 만들었으니 사용하기 위해선 app.module.ts에 등록해주어야 한다.
9.2.2. chats.module.ts 의 TypeOrmModule.forFeature 내에 추가하기
@Module({
imports: [
TypeOrmModule.forFeature([ChatsModel, MessagesModel]),
CommonModule,
],
ChatsController의 sendMessage 메서드에서 MessagesModel을 불러와서 사용해야하기 때문에 forFeature내에 MessagesModel을 추가해준다.
9.3. CreateMessagesDto 구현하기
import { PickType } from "@nestjs/mapped-types";
import { MessagesModel } from "../entity/messages.entity";
import { IsNumber } from "class-validator";
export class CreateMessagesDto extends PickType(MessagesModel, [
'message',
]) {
@IsNumber()
chatId: number;
@IsNumber()
authorId: number;
}
Dto는 클라이언트에서 보내는 필드와 동일한 이름으로 프로퍼티를 구성하면 인스턴스의 형태로 데이터를 전송하는 객체를 의미한다.
메세지를 생성할 때 클라이언트는 chatId와 authorId, message를 작성하여 서버로 보내도록 하기 위해 CreateMessagesDto를 구현한다.
9.4. ChatsMessagesService 서비스 구현하기
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { MessagesModel } from "./entity/messages.entity";
import { FindManyOptions, Repository } from "typeorm";
import { CommonService } from "src/common/common.service";
import { BasePaginationDto } from "src/common/dto/base-pagination.dto";
import { CreateMessagesDto } from "./dto/create-messages.dto";
@Injectable()
export class ChatsMessagesService {
constructor(
@InjectRepository(MessagesModel)
private readonly messagesRepository: Repository<MessagesModel>,
private readonly commonService: CommonService,
) {}
async createMessage(
dto: CreateMessagesDto,
) {
const message = await this.messagesRepository.save({
chat: {
id: dto.chatId,
},
author: {
id: dto.authorId,
},
message: dto.message,
});
return this.messagesRepository.findOne({
where: {
id: message.id,
},
relations: {
chat: true,
}
});
}
paginateMessages(
dto: BasePaginationDto,
overrideFindOptions: FindManyOptions<MessagesModel>,
) {
return this.commonService.paginate(
dto,
this.messagesRepository,
overrideFindOptions,
'messages',
)
}
}
메세지를 생성하거나 메세지를 보여주는 기능을 하는 것들을 만들기 위해 ChatsMessagesService라는 클래스를 구현한다. createMessage 메서드는 MessagesModel을 생성하고 반환한다.
paginateMessages 메서드는 메세지들을 Pagination하여 반환한다.
9.5. chats.module.ts 의 provider에 ChatsMessagesService 등록하기
providers: [ChatsGateway, ChatsService, ChatsMessagesService],
Chat 내에서 ChatsMesssagesService의 내부 로직들을 사용하려면, 모듈의 providers에 등록해주어야 한다.
9.6. chat.gateway.ts 의 sendMessage 메서드 수정하기
@SubscribeMessage('send_message')
async sendMessage(
@MessageBody() dto: CreateMessagesDto,
@ConnectedSocket() socket: Socket,
) {
const chatExists = await this.chatsService.checkIfChatExists(dto.chatId);
if(!chatExists) {
throw new WsException({
code: 100,
message: `존재하지 않는 chat 입니다. chatId: ${dto.chatId}`,
});
}
const message = await this.messagesService.createMessage(dto) as MessagesModel;
// broadcast
socket.to(message.chat.id.toString()).emit('receive_message', message.message);
// room 통신
// this.server.in(message.chatId.toString()).emit('receive_message', message.message);
}
클라이언트에서 메세지를 보내면 sendMessage가 호출되는데, 이 부분에서 messageService.createMessage 를 호출하여 메세지를 생성하고 socket.to 를 통해 해당 채팅방의 소켓들에게 메세지를 보내게 된다.
9.7. MessagesController 구현하기
import { Controller, Get, Param, ParseIntPipe, Query } from "@nestjs/common";
import { ChatsMessagesService } from "./messages.service";
import { BasePaginationDto } from "src/common/dto/base-pagination.dto";
@Controller('chats/:cid/messages')
export class MessagesController {
constructor(
private readonly messagesService: ChatsMessagesService,
) {}
@Get()
paginateMessage(
@Param('cid', ParseIntPipe) id: number,
@Query() dto: BasePaginationDto,
) {
return this.messagesService.paginateMessages(
dto,
{
where: {
chat: {
id,
}
},
relations: {
author: true,
chat: true,
}
}
);
}
}
클라이언트에서 모든 메세지들을 가져와 볼 수 있는 엔드포인트를 만들기 위해 MessagesController를 구현한다.
9.8. MessagesController 모듈에 등록하기
controllers: [ChatsController, MessagesController],
Message에는 모듈이 없기 때문에 chats.module.ts 의 controllers에 MessagesController를 등록한다.
9.9. 포스트맨으로 테스트하기
9.9.1. User들 연결 후 채팅방에 입장
User들을 연결 후 채팅방에 입장시키기 전에 채팅방이 생성되지 않은 경우, 우리가 구현한 createChat을 통해 채팅방을 생성할 수 있다. CreateChatDto는 userIds를 배열로 받으므로 위와 같이 작성 후 create_chat으로 Send하면 채팅방의 아이디가 1, 2인 두 개의 채팅방이 생성된다.
모든 User들을 2번방에 입장시킨다.
9.9.2. 메세지 보내기
User 1에서 위와 같이 작성한 후, send_message를 하면 메세지를 보낼 수 있다. 참고로 본인은 제외하고 채팅방 내의 다른 인원에게 메세지를 보내게 된다. (브로드캐스팅)
User 2가 메세지를 받은 것을 볼 수 있다.
User 3도 메세지를 받았다.
9.9.3. 메세지 확인하기
MessagesController에서 paginateMessage 메서드를 만들었기 때문에 포스트맨에서 확인이 가능하다.
cid 에 2를 넣으면 2번 채팅방에 저장된 메세지들을 볼 수 있다.
메세지를 보내면 DB에 쌓이도록 구현했기 때문에 마찬가지로 DB에서 메세지를 확인이 가능하다.
'NestJS' 카테고리의 다른 글
[NestJS] 모듈 네스팅 (0) | 2025.03.12 |
---|---|
[NestJS] SocketIO 심화 (1) | 2025.03.12 |
[NestJS] Middleware (0) | 2025.03.11 |
[NestJS] Exception Filter (0) | 2025.03.10 |
[NestJS] Interceptor (0) | 2025.03.10 |