🎉 berenickt 블로그에 온 걸 환영합니다. 🎉
Back
NestJs
31-socketIO-기본

1. Websocket 이론

  • 채팅 서비스, 주식 관련 서비스, 가상화폐 서비스 같은 걸새로고침을 하지 않아도 서버에서 정보를 던져준다.
  • 웹소켓을 사용하시면. 이런 리얼타임 서비스를 저희가 구현할 수 있다.
1
// ************* 기존 클라이언트-서버 구조
2
┌───────────┐ ┌───────────┐
3
│ │ ───────── (요청) ───────> │ │
4
│ 클라이언트 │ │ 서버 │
5
│ │ <──────── (응답) ──────── │ │
6
└───────────┘ └───────────┘
  • 기존의 클라이언트-서버 구조의 문제점은 단일 방향이라는 점이다.
  • 즉, 클라이언트에서부터 요청을 보내야지만, 요청한 거에 대한 응답을 받을 수 있다.
1
// ************* WebSocket
2
┌───────────┐ ┌───────────┐
3
│ │ <───── (요청, 응답) ─────> │ │
4
│ 클라이언트 │ │ 서버 │
5
│ │ <───── (요청, 응답) ─────> │ │
6
└───────────┘ └───────────┘
  • e.g.) 채팅은 누가 메시지를 보내면, 사용자 따로 뭘 하지 않아도(요청하지 않아도) 메시지가 온다.
  • 이것이 real-time communication이고, 웹소켓이 해결해준다.
  • 그래서 이제는 양방향으로 커뮤니케이션을 할 수가 있게 된다.

2. Socket IO 이론

bidirectional-communication2

  • 공식문서 : https://socket.io/docs/v4/
  • Socket IO는 Websocket 프로토콜을 사용해서 만든
    • low-latency(낮은 지연시간), bidirectional(양방향 소통), event based(이벤트 기반)으로
    • 클라이언트와 서버가 통신할 수 있게 해주는 기능이다.
  • 즉, Socket IO는 결국에 웹소켓이다.

2.1 기본적인 통신법. emit & on

  • emit : 메시지를 보내는 것
  • on : 보낸 메시지를 리스닝하는 것

서버 코드

server
1
import { Server } from 'socket.io'
2
3
// Socekt.IO 서버 생성
4
const io = new Server(3000)
5
6
/** 📌 on() : 클라이언트가 서버에 연결되면 실행되는 함수 정의
7
* on 함수를 실행하면 특정 이벤트(1번쨰 파라미터)가 있을떄,
8
* 콜백 함수를 실행할 수 있으며, (2번쨰 파라미터)
9
* 해당 콜백 함수는 메시지를 1번쨰 파라미터로 받는다.
10
* connection 이벤트는 미리 정의된 이벤트로 "연결됐을 떄" 실행한다.
11
*/
12
io.on('connection', socket => {
13
/** 📌 emit() : 메시지 보내기
14
* 1번 파라미터, 이벤트 이름
15
* 2번~이후 파라미터, 메시지
16
*/
17
socket.emit('hello_form_server', 'this is message from server')
18
19
socket.on('hello_from_client', message => {
20
// 'this is message from clinet'
21
console.log(message)
22
})
23
})

클라이언트 코드

client
1
import { io } from 'socket.io-client'
2
3
// 📌 (1) Socket.IO 서버에 연결
4
const socket = io('ws://localhost:3000') // ws는 웹소캣의 약어
5
6
// 'hello_from_clint' 이벤트를 듣고 있는 소켓에 메시지 보내기
7
socket.emit('hello_form_server', 'this is message from server')
8
9
// 'hello_from_server' 이벤트로 메시지가 오면 함수 실행하기
10
socket.on('hello_from_client', message => {
11
// 'this is message from clinet'
12
console.log(message)
13
})
  • 웹소켓 연결은 무조건 클라이언트에서 먼저 시작한다.
  • 연결한 뒤에는 메시지를 서버가 먼저 보내든, 클라이언트가 먼저 보내든 상관없다.

2.2 Acknowledgement

Acknowledgement는 한마디로 메시지를 잘 받았다고 ok 신호를 보내주는 겁니다.

서버 코드

server
1
/**
2
* 'hello' 룸에 'world'라는 메시지를 보낸다.
3
* 3번쨰 파라미터는 콜백 함수로 ㅁcknowledgment가 오면 실행한다.
4
*/
5
socket.emit('hello', 'world', response => {
6
console.log(response) // 수산 양호
7
})

클라이언트 코드

client
1
/**
2
* 1번쨰 파라미터 : 이벤트 이름
3
* 2번쨰 파라미터 : 메시지가 왔을 떄 실행할 함수
4
* 함수는 1번째 파라미너톨 메시지,
5
* 2번쨰 파라미터로 수신 응답할 수 있는 콜백함수가 주어짐
6
*/
7
socket.on('hello', (message, callback) => {
8
console.log(message) // world
9
callback('수신 양호') // 📌 emit을 날린 곳으로 다시 돌아감
10
})

2.3 Namespace & Room

namespace-room

  • 클라이언트(모바일, 웹)가 서버에 socket.io 요청들을 넣을떄,
    • 아무것도 정의하지 않으면 namespace가 기본으로 /가 정의된다.
    • 아무것도 넣지 않고 emit하면 / namespace로 정의된다.
  • REST API에서 path를 짜듯이, Namespace라는 거를 만들게 된다.
    • 여러 개의 Namespace가 있으면, 원하는 Namespace를 골라서 요청할 수 있다
  • 근데 서버에서는 namespace 안에서 또 Room으로 나눌 수가 있다.
    • /chat에 요청을 넣고 채팅을 한다.
    • 이떄 /chat의 각각의 룸들은 카톡에서 각각의 채팅방 리스트와 같다.
    • 그리고 /noti/room1/chat/room1은 완전 다르고, 연결이 안된다.

2.4 Namespace & Room (Server)

1
/***
2
* of를 이용하면 namespace를 정할 수 있다.
3
* namespace는 일반적으로 라우트 형태를 따라 지정한다.
4
*/
5
const chatNamespace = io.of('/chat')
6
7
// chatNmaespace에 연결된 소켓만 아래 코드가 실행된다.
8
chatNamespace.on('connection', socket => {
9
/***
10
* 현재 연결된 socket을 room1에 연결한다.
11
* 이 room1은 /chat namespace에만 존재하는 room이다.
12
*/
13
socket.join('room1')
14
chatNamespace.to('room1').emit('hello', 'world')
15
})
16
17
// /noti namespace 생성
18
const notiNamespace = io.of('/noti')
19
20
// /noti chatNmaespace에 연결된 소켓만 실행된다.
21
chatNamespace.on('connection', socket => {
22
/***
23
* 이 room1은 /chat namespace의 room1과 전혀 관련이 없다.
24
* 다른 namespace의 room1에는 들어갈 수 없다.
25
*/
26
socket.join('room1')
27
28
// 역시나 /noti namespace의 room1에만 메시지를 보낸다.
29
chatNamespace.to('room1').emit('hello', 'world')
30
})

2.5 Namespace & Room (Client)

1
// 기본 namespace로 연결 -> /
2
const socket = io('ws://localhost:3000') // ws는 웹소캣의 약어
3
4
// 기본 namespace로 연결 -> /chat
5
const chatSocket = io('ws://localhost:3000/chat')
6
7
// 기본 namespace로 연결 -> /noti
8
const notiSocket = io('ws://localhost:3000/noti')
9
10
/**
11
* client에서는 room을 정할 수 있는 기능이 없다.
12
* room은 서버에서만 socket.join()을 실행해서,
13
* 특정 room에 들어가도록 할 수 있다.
14
*/

2.6 Emit & Broadcast

1
// 연결된 모든 socket들에게 메시지를 보낸다
2
socket.emit('hello', 'world')
3
4
// 나 빼고 모두에게 메시지를 보낸다
5
socket.broadcast.emit('hello', 'world')

3. Gateway 생성하고 메시지 리스닝하기

먼저 3개의 패키지를 설치한다.

1
yarn add @nestjs/websockets @nestjs/platform-socket.io socket.io

그리고 버전 충돌을 막기 위해 nest 패키지들을 다시 설치한다. 이렇게 아래 패키지들을 다시설치하면 메이저 버전들이 맞춰진다.

  • @nestjs/common @nestjs/core @nestjs/jwt @nestjs/platform-express
  • @nestjs/platform-socket.io @nestjs/typeorm @nestjs/websockets
  • 그러면 이 패키지들에 ^10.3.0이런 식으로 앞에 ^(캐럿)이 붙는다.
1
yarn add @nestjs/common @nestjs/core @nestjs/jwt @nestjs/platform-express @nestjs/platform-socket.io @nestjs/typeorm @nestjs/websockets

이제 chats resource를 생성한다.

1
$ nest g resource
2
? What name ? chats
3
? What transport layer do you use? REST API
4
? Would you like to generate CRUD entry points? No

chats/chats.gateway.ts 파일을 만든다.

chats/chats.gateway.ts
1
import { MessageBody, OnGatewayConnection, SubscribeMessage, WebSocketGateway } from '@nestjs/websockets'
2
import { Socket } from 'socket.io'
3
4
@WebSocketGateway({
5
// ws://localhost:3000/chats
6
namespace: 'chats',
7
})
8
export class ChatsGateway implements OnGatewayConnection {
9
handleConnection(socket: Socket) {
10
console.log(`on connect called : ${socket.id}`)
11
}
12
13
// socket.on('send_message', (message)=>{ console.log(message)})
14
@SubscribeMessage('send_message')
15
sendMessage(@MessageBody() message: string) {
16
console.log(message)
17
}
18
}

위에서 만든 gateway를 등록하기 위해 chats 모듈에서 provider에 넣어준다.

chats.module.ts
1
import { Module } from '@nestjs/common'
2
import { ChatsService } from './chats.service'
3
import { ChatsController } from './chats.controller'
4
import { ChatsGateway } from './chats.gateway'
5
6
@Module({
7
controllers: [ChatsController],
8
providers: [ChatsGateway, ChatsService],
9
})
10
export class ChatsModule {}

postman에 위쪽 new 버튼을 클릭하면, socket.io가 있다.

  • 새 프로젝트를 만들고. socket.io 요청으로 User 1 /chats라고 만든다.
  • ws://{{host}}/chats로 connect 요청을 보낸다.
  • 웹소켓 서버가 잘 생성되었으면, 터미널창에 아래와 같이 잘 연결되었다고 미리 작성해둔 메시지가 뜬다.
    • on connect called : xaTkfZRExjMqwUr0AAAB
  • 포스트맨에서 emit하는 법은 Message 탭에서 써주고, 아래 Ack에 이벤트명을 넣어주면 된다.
    • Message에 적을 내용 : hello form clinet
    • Ack 이벤트명 : send_message

4. 서버에서 이벤트 전송

이 namespace에 연결된 모든 소켓들한테 메시지를 보내는 기능을 만들자.

chats.gateway.ts
1
import {
2
MessageBody,
3
OnGatewayConnection,
4
SubscribeMessage,
5
WebSocketGateway,
6
WebSocketServer,
7
} from '@nestjs/websockets'
8
import { Server, Socket } from 'socket.io'
9
10
@WebSocketGateway({
11
// ws://localhost:3000/chats
12
namespace: 'chats',
13
})
14
export class ChatsGateway implements OnGatewayConnection {
15
@WebSocketServer()
16
server: Server
17
18
handleConnection(socket: Socket) {
19
console.log(`on connect called : ${socket.id}`)
20
}
21
22
// socket.on('send_message', (message)=>{ console.log(message)})
23
@SubscribeMessage('send_message')
24
sendMessage(@MessageBody() message: string) {
25
this.server.emit('receive_message', 'hello from server')
26
}
27
}

포스트맨에서 서버에서 오는 메시지를 리스닝하려면,

  • Events 탭에서 파라미터를 넣는 것처럼 이벤트를 넣어주면 된다.
EVENTSLISTENDESCRIPTION
receive_message체크

포스트맨에서 socket.io 요청을 만들고 User 2 /chats 라고 짓는다.

  • 그리고 User 2 /chats의 Events 탭을 다음과 같이 만든다.
EVENTSLISTENDESCRIPTION
receive_message체크

그리고 터미널 창에 2개의 socket.io 연결을 보면, 두 소켓이 다른 값인 걸 알 수 있다.

  • 또 포스트맨에서 socket.io 요청을 만들고 User 3 /chats 라고 짓는다.
  • 마찬가지로 User 3 /chats의 Events 탭을 위와 똑같이 같이 만든다.
EVENTSLISTENDESCRIPTION
receive_message체크

5. Room 활용하기

이번에는 방을 나눠 가지고 특정 방에 들어와 있는 사용자만 메시지를 받을 수 있도록 합니다.

  • 각각의 채팅방 별로 메시지를 보내기,
  • 그럴려면 채팅방에 들어가게 하는 기능이 필요하다,
chats.gateway.ts
1
// chats.gateway.ts 생략
2
@SubscribeMessage('enter_chat')
3
enterChat(
4
@MessageBody() data: number[], //
5
@ConnectedSocket() socket: Socket,
6
) {
7
for (const chatId of data) {
8
socket.join(chatId.toString())
9
}
10
}
11
12
// socket.on('send_message', (message)=>{ console.log(message)})
13
@SubscribeMessage('send_message')
14
sendMessage(@MessageBody() message: { message: string; chatId: number }) {
15
// 방에 들어간 사용자에게만 메시지 보내기
16
this.server
17
.in(message.chatId.toString()) //
18
.emit('receive_message', message.message)
19
}

6. Broadcasting

Broadcasting : 나를 제외하고 다른사람들한테만 보내는 것

chats.gateway.ts
1
// chats.gateway.ts 생략
2
@SubscribeMessage('send_message')
3
sendMessage(
4
@MessageBody() message: { message: string; chatId: number },
5
@ConnectedSocket() socket: Socket, //
6
) {
7
// **** socket은 현재 연결된 socket을 의미 (나를 제외하고 다른사람들한테만 보내기)
8
socket
9
.to(message.chatId.toString()) //
10
.emit('receive_message', message.message)
11
12
// **** 서버 전체 사용자에게만 메시지 보내기
13
// this.server
14
// .in(message.chatId.toString()) //
15
// .emit('receive_message', message.message)
16
}

7. Chat Entity 생성

방을 만드는 API를 만든다.

채팅생성 DTO를 위해 src/chats/dto/create-chat.dto.ts 파일을 만든다.

src/chats/dto/create-chat.dto.ts
1
import { IsNumber } from 'class-validator'
2
3
export class CreateChatDto {
4
@IsNumber({}, { each: true })
5
userIds: number[]
6
}

src/chats/entity/chats.entity.ts 파일을 만든다.

src/chats/entity/chats.entity.ts
1
import { Entity, ManyToMany } from 'typeorm'
2
import { UsersModel } from '../../users/entities/users.entity'
3
import { BaseModel } from 'src/common/entities/base.entity'
4
5
@Entity()
6
export class ChatsModel extends BaseModel {
7
@ManyToMany(() => UsersModel, user => user.chats)
8
users: UsersModel[]
9
}

users 모델에 가서 다대다 관계로 연결할 chats 프로퍼티를 추가한다.

src/users/entities/users.entity.ts
1
// src/users/entities/users.entity.ts 생략
2
@ManyToMany(() => ChatsModel, chat => chat.users)
3
@JoinTable()
4
chats: ChatsModel[]

모듈의 위치를 찾아주게 하기 위해, app 모듈에서 chats 모델을 넣는다.

src/app.module.ts
1
// entities폴더에 작성한 PostsModel 가져오기
2
entities: [PostsModel, UsersModel, ImageModel, ChatsModel],

이제 typeorm에서 사용할거니 chats 모델에서 import 해준다.

src/chats/chats.module.ts
1
import { Module } from '@nestjs/common'
2
import { ChatsService } from './chats.service'
3
import { ChatsController } from './chats.controller'
4
import { ChatsGateway } from './chats.gateway'
5
import { TypeOrmModule } from '@nestjs/typeorm'
6
import { ChatsModel } from './entity/chats.entity'
7
8
@Module({
9
imports: [TypeOrmModule.forFeature([ChatsModel])],
10
controllers: [ChatsController],
11
providers: [ChatsGateway, ChatsService],
12
})
13
export class ChatsModule {}

그리고 chats 서비스에 기능을 추가한다.

src/chats/chats.service.ts
1
import { Injectable } from '@nestjs/common'
2
import { InjectRepository } from '@nestjs/typeorm'
3
import { ChatsModel } from './entity/chats.entity'
4
import { Repository } from 'typeorm'
5
import { CreateChatDto } from './dto/create-chat.dto'
6
7
@Injectable()
8
export class ChatsService {
9
constructor(
10
@InjectRepository(ChatsModel)
11
private readonly chatsRepository: Repository<ChatsModel>,
12
) {}
13
14
async createChat(dto: CreateChatDto) {
15
const chat = await this.chatsRepository.save({
16
users: dto.userIds.map(id => ({ id })),
17
})
18
19
return this.chatsRepository.findOne({
20
where: { id: chat.id },
21
})
22
}
23
}

이제 이 기능을 gateway에서 사용한다.

src/chats/chats.gateway.ts
1
// 생략
2
@WebSocketGateway({
3
// ws://localhost:3000/chats
4
namespace: 'chats',
5
})
6
export class ChatsGateway implements OnGatewayConnection {
7
constructor(private readonly chatService: ChatsService) {}
8
9
// 생략
10
11
@SubscribeMessage('create_chat')
12
async createChat(@MessageBody() data: CreateChatDto) {
13
const chat = await this.chatService.createChat(data)
14
}
15
16
// 생략
17
}

8. Pagination Chat API 생성

chats/dto/paginate-chat.dto.ts 파일을 만든다.

src/chats/dto/paginate-chat.dto.ts
1
import { BasePaginationDto } from 'src/common/dto/base-pagination.dto'
2
3
export class PaginateChatDto extends BasePaginationDto {}

작성한 DTO를 chats 서비스에 적용한다.

src/chats/chats.service.ts
1
// 생략
2
@Injectable()
3
export class ChatsService {
4
constructor(
5
@InjectRepository(ChatsModel)
6
private readonly chatsRepository: Repository<ChatsModel>,
7
private readonly commonService: CommonService,
8
) {}
9
10
paginateChats(dto: PaginateChatDto) {
11
return this.commonService.paginate(
12
dto, //
13
this.chatsRepository,
14
{ relations: { users: true } },
15
'chats',
16
)
17
}
18
// 생략
19
}

chats 컨트롤러에 엔드포인트를 추가한다.

src/chats/chats.controller.ts
1
import { Controller, Get, Query } from '@nestjs/common'
2
import { ChatsService } from './chats.service'
3
import { PaginateChatDto } from './dto/paginate-chat.dto'
4
5
@Controller('chats')
6
export class ChatsController {
7
constructor(private readonly chatsService: ChatsService) {}
8
9
@Get()
10
paginateChat(@Query() dto: PaginateChatDto) {
11
return this.chatsService.paginateChats(dto)
12
}
13
}

그리고 chats 모듈에 Common모듈을 import해준다.

src/chats/chats.module.ts
1
// 생략
2
@Module({
3
imports: [
4
TypeOrmModule.forFeature([ChatsModel]), //
5
CommonModule,
6
],
7
controllers: [ChatsController],
8
providers: [ChatsGateway, ChatsService],
9
})
10
export class ChatsModule {}

9. Enter Chat 이벤트 업데이트 & WSException 던지기

Enterchat DTO를 구현하기 위해 chats/dto/enter-chat.dto.ts파일을 만든다.

src/chats/dto/enter-chat.dto.ts
1
import { IsNumber } from 'class-validator'
2
3
export class EnterChatDto {
4
@IsNumber({}, { each: true })
5
chatIds: number[]
6
}

chats 서비스에 진짜 채팅방을 나갈건지 체크하는 기능을 추가한다.

src/chats/chats.service.ts
1
// chats/chats.service.ts 생략
2
async checkIfChatExists(chatId: number) {
3
const exists = await this.chatsRepository.exist({
4
where: {
5
id: chatId,
6
},
7
})
8
return exists
9
}

작성한 기능을 chats gateway에 적용한다

src/chats/chats.gateway.ts
1
// src/chats/chats.gateway.ts 생략
2
@SubscribeMessage('enter_chat')
3
async enterChat(@MessageBody() data: EnterChatDto, @ConnectedSocket() socket: Socket) {
4
for (const chatId of data.chatIds) {
5
const exists = await this.chatService.checkIfChatExists(chatId)
6
if (!exists) {
7
throw new WsException({
8
code: 100,
9
message: `존재하지 않는 채팅방입니다! ::: ChatID: ${chatId}`,
10
})
11
}
12
}
13
socket.join(data.chatIds.map(id => id.toString()))
14
}

10. 메시지 보내기 마무리

chats/messages/entity/messages.entity.ts 파일을 만든다.

src/chats/messages/entity/messages.entity.ts
1
import { IsString } from 'class-validator'
2
import { ChatsModel } from 'src/chats/entity/chats.entity'
3
import { BaseModel } from 'src/common/entities/base.entity'
4
import { UsersModel } from 'src/users/entities/users.entity'
5
import { Column, Entity, ManyToOne } from 'typeorm'
6
7
@Entity()
8
export class MessagesModel extends BaseModel {
9
@ManyToOne(() => ChatsModel, chat => chat.messages)
10
chat: ChatsModel
11
12
@ManyToOne(() => UsersModel, user => user.messages)
13
author: UsersModel
14
15
@Column()
16
@IsString()
17
message: string
18
}

src/chats/entity/chats.entity.ts에서 연결해준다.

src/chats/entity/chats.entity.ts
1
import { Entity, ManyToMany, OneToMany } from 'typeorm'
2
import { UsersModel } from '../../users/entities/users.entity'
3
import { BaseModel } from 'src/common/entities/base.entity'
4
import { MessagesModel } from '../messages/entitiy/messages.entity'
5
6
@Entity()
7
export class ChatsModel extends BaseModel {
8
@ManyToMany(() => UsersModel, user => user.chats)
9
users: UsersModel[]
10
11
@OneToMany(() => MessagesModel, message => message.chat)
12
messages: MessagesModel[]
13
}

src/users/entities/users.entity.ts에도 관계 연결해준다.

src/users/entities/users.entity.ts
1
// src/users/entities/users.entity.ts 생략
2
@OneToMany(() => MessagesModel, message => message.author)
3
messages: MessagesModel[]

그리고 app 모듈에서 MessagesModel을 넣어준다

src/app.module.ts
1
// src/app.module.ts 생략
2
entities: [
3
PostsModel, //
4
UsersModel,
5
ImageModel,
6
ChatsModel,
7
MessagesModel,
8
],

src/chats/messages/dto/create-message.dto.ts 파일을 만든다.

src/chats/messages/dto/create-message.dto.ts
1
import { PickType } from '@nestjs/mapped-types'
2
import { IsNumber } from 'class-validator'
3
import { MessagesModel } from '../entitiy/messages.entity'
4
5
export class CreateMessagesDto extends PickType(MessagesModel, ['message']) {
6
@IsNumber()
7
chatId: number
8
}

src/chats/messages/messages.service.ts 파일을 만든다.

src/chats/messages/messages.service.ts
1
import { Injectable } from '@nestjs/common'
2
import { InjectRepository } from '@nestjs/typeorm'
3
import { FindManyOptions, Repository } from 'typeorm'
4
import { CommonService } from 'src/common/common.service'
5
import { BasePaginationDto } from 'src/common/dto/base-pagination.dto'
6
import { MessagesModel } from './entitiy/messages.entity'
7
import { CreateMessagesDto } from './dto/create-messages.dto'
8
9
@Injectable()
10
export class ChatsMessagesService {
11
constructor(
12
@InjectRepository(MessagesModel)
13
private readonly messagesRepository: Repository<MessagesModel>,
14
private readonly commonService: CommonService,
15
) {}
16
17
async createMessage(dto: CreateMessagesDto, authorId: number) {
18
const message = await this.messagesRepository.save({
19
chat: {
20
id: dto.chatId,
21
},
22
author: {
23
id: authorId,
24
},
25
message: dto.message,
26
})
27
28
return this.messagesRepository.findOne({
29
where: {
30
id: message.id,
31
},
32
relations: {
33
chat: true,
34
},
35
})
36
}
37
38
paginteMessages(dto: BasePaginationDto, overrideFindOptions: FindManyOptions<MessagesModel>) {
39
return this.commonService.paginate(
40
dto, //
41
this.messagesRepository,
42
overrideFindOptions,
43
'messages',
44
)
45
}
46
}

chats 모듈에다가 등록해준다.

chats.module.ts
1
// 생략
2
@Module({
3
imports: [
4
TypeOrmModule.forFeature([ChatsModel, MessagesModel]), //
5
CommonModule,
6
],
7
controllers: [ChatsController],
8
providers: [
9
ChatsGateway,
10
ChatsService, //
11
ChatsMessagesService,
12
],
13
})
14
export class ChatsModule {}

chats.gateway를 수정한다.

src/chats/chats.gateway.ts
1
// 생략
2
@WebSocketGateway({
3
// ws://localhost:3000/chats
4
namespace: 'chats',
5
})
6
export class ChatsGateway implements OnGatewayConnection {
7
constructor(
8
private readonly chatsService: ChatsService,
9
private readonly messageService: ChatsMessagesService,
10
) {}
11
// 생략
12
@SubscribeMessage('send_message')
13
async sendMessage(@MessageBody() dto: CreateMessagesDto, @ConnectedSocket() socket: Socket) {
14
const chatExists = await this.chatsService.checkIfChatExists(dto.chatId)
15
16
if (!chatExists) {
17
throw new WsException({
18
code: 100,
19
message: `존재하지 않는 채팅방입니다! ::: ChatID: ${dto.chatId}`,
20
})
21
}
22
23
const message = await this.messageService.createMessage(dto)
24
socket.to(message.chat.id.toString()).emit('receive_message', message.message)
25
}
26
}

messages.controller.ts 파일을 만든다.

messages.controller.ts
1
import { Controller, Get, Param, ParseIntPipe, Query } from '@nestjs/common'
2
import { ChatsMessagesService } from './messages.service'
3
import { BasePaginationDto } from 'src/common/dto/base-pagination.dto'
4
5
@Controller('chats/:cid/messages')
6
export class MessagesController {
7
constructor(private readonly messagesService: ChatsMessagesService) {}
8
9
@Get()
10
paginateMessage(
11
@Param('cid', ParseIntPipe) id: number, //
12
@Query() dto: BasePaginationDto,
13
) {
14
return this.messagesService.paginteMessages(dto, {
15
where: {
16
chat: {
17
id,
18
},
19
},
20
relations: {
21
author: true,
22
chat: true,
23
},
24
})
25
}
26
}