[포스코x코딩온] 웹 풀스택 과정 7기 11주차 금요일 회고
목차
nest.js
지난 주말동안 열심히 nest.js 에 대해 공부를 했다.
nest 의 특징 중 하나라면 CLI가 있다는 것이었다.
CLI를 통해 nest 프로젝트를 생성하고, 컨트롤러, 서비스, 모듈 등을 생성할 수 있었다.
이미 정의되어 있는 데코레이터를 통해 간단하게 라우팅을 정의하는 등 손쉽게 컨트롤러를 정의할 수 있었다.
또 DTO(Data Transfer Object)를 통해 데이터를 검증하고 변환하기도 편리했다.
다양한 기능이 구현되어 있는 것을 보며, 이번 프로젝트는 정말 재밌게 진행될 것 같다는 생각이 들었다.
하지만 정말 예상치 못한 문제가 발생했다...
팀원 분의 탈주
팀원 분 한 분이 늦게까지 오질 않으셨다.
그냥 개인적인 사정이 있나보다 싶었는데, 강사님께서 청천벽력과 같은 소식을 전해주셨다.
팀원 분이 탈주하셨다는 소식이었다...
🥲
너무 당황스러웠다.
정황은 듣지 못했지만, 아마 취업을 해서 조기 졸업(?)을 하신 것 같았다.
안 그래도 다른 팀은 모두 4명이고 우리만 3명이라는 적은 팀원 수로 시작했는데, 그마저도 한 분이 나가시다니...
그 분에게는 정말 잘 된 일이었지만, 남은 우리 2명은 당황스러웠다. 😭
하지만 당황해할 시간도 없었다.
이젠 2명 밖에 없기 때문이었다.
Prisma
nest에서 Prisma ORM 을 사용하기 위해서는 다음과 같은 Prisma 서비스가 필요했다.
import { INestApplication, Injectable, OnModuleInit } from "@nestjs/common";
import { PrismaClient } from "@prisma/client";
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
async enableShutdownHooks(app: INestApplication) {
this.$on("beforeExit", async () => {
await app.close();
});
}
}
이후 DB가 필요한 모듈에서 PrismaService를 주입받아 사용할 수 있었다.
DTO
DTO(Data Transfer Object)는 데이터를 검증하고 변환하기 위해 사용된다.
DTO를 사용하기 위해서는 먼저 class-validator
와 class-transformer
를 설치해야 한다.
이후 DTO를 정의할 수 있다.
import { IsString } from "class-validator";
export class CreateUserDto {
@IsString()
username: string;
@IsString()
password: string;
}
또한 앱에 전역적으로 사용할 수 있도록 ValidationPipe
를 설정해야 한다.
import { ValidationPipe } from "@nestjs/common";
...
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // DTO에 정의되지 않은 속성이 들어오면 무시
forbidNonWhitelisted: true, // DTO에 정의되지 않은 속성이 들어오면 요청을 막음
transform: true, // 요청 데이터를 DTO 타입으로 변환
}));
...
이렇게 작성하면 요청 데이터를 검증하고, DTO 타입으로 변환해서 컨트롤러에서 바로 사용이 가능했다.
소켓
먼저 nest에서 소켓을 사용하기 위해선 gateway를 정의해야 한다.
@WebSocketGateway( // gateway 데코레이터
81, // gateway가 사용할 포트
{ transports: ["websocket"] }, // 사용할 프로토콜
)
export class ChatsGateway
implements OnGatewayConnection, // 클라이언트가 연결되었을 때
OnGatewayDisconnect, // 클라이언트가 연결이 끊겼을 때
OnGatewayInit // gateway가 초기화되었을 때
{
rooms: Map<string, RoomDto> = new Map(); // 소켓 방 목록
@WebSocketServer()
server: Server;
afterInit(server: Server) {
// gateway가 초기화되었을 때 실행
}
handleConnection(client: Socket) {
// 클라이언트가 연결되었을 때 실행
}
handleDisconnect(client: Socket) {
// 클라이언트가 연결이 끊겼을 때 실행
}
@SubscribeMessage("<이벤트 이름>") // 클라이언트가 <이벤트 이름> 이벤트를 보냈을 때 실행
handleCreate(
@MessageBody("<메세지 속성>") attr: MessageAttr, // 클라이언트가 보낸 데이터 중 <메세지 속성> 속성
@MessageBody() message: MessageDto, // 클라이언트가 보낸 데이터를 <MessageDto> 타입으로 변환
@ConnectedSocket() client: Socket, // 클라이언트 소켓
) {
const id = this.rooms.size + 1; // 방 ID
client.join(id); // 클라이언트를 <id> 방에 입장시킴
client.emit("<이벤트 이름>", message); // 클라이언트에게 <이벤트 이름> 이벤트를 보냄
client.to(message.roomId).emit("<이벤트 이름>", message); // <id> 방에 <이벤트 이름> 이벤트를 보냄
return id; // 클라이언트에게 <id>를 반환
}
}
Swagger UI
Django에서 DRF를 사용하면 자동으로 API 문서를 생성해주는 기능이 있었다.
Nest에서는 그런 기능이 없는 것 같아서 찾아보니, Swagger UI를 사용하면 된다고 한다.
좀만 더 빨리 찾아볼걸 싶었을 정도로 너무 편리했다.
먼저 @nestjs/swagger
를 설치한다.
그리고 src/main.ts
에서 앱에 다음을 추가한다.
...
import { DocumentBuilder } from "@nestjs/swagger";
import { SwaggerModule } from "@nestjs/swagger";
async function bootstrap() {
...
const swaggerConfig = new DocumentBuilder()
.setTitle("Swagger Example")
.setDescription("Swagger study API description")
.setVersion("1.0.0")
.addTag("swagger")
.addBearerAuth(
{
type: "http",
scheme: "bearer",
name: "JWT",
in: "header",
},
"access",
)
.build();
const document = SwaggerModule.createDocument(app, swaggerConfig);
SwaggerModule.setup("swagger", app, document);
...
}
코드가 조금 길어지는 것 같아서 나는 swaggerConfig 정의 부분을 따로 분리했다.
이후 DTO에 다음과 같이 작성하면, Swagger UI에도 반영된다.
...
import { ApiProperty } from "@nestjs/swagger";
export class UsernameDto {
@ApiProperty({
description: "유저의 username", // 요소 설명
example: genString(), // 예시 값
type: String, // 타입
})
@IsString()
readonly username: string;
}
또 컨트롤러 클래스에 다음과 같이 작성하면, 컨트롤러 별로 API 문서를 분리할 수 있다.
...
import { ApiTags } from "@nestjs/swagger";
@ApiTags("<컨트롤러 태그>") // 컨트롤러에 <컨트롤러 태그> 태그를 붙임
@Controller("<컨트롤러 경로>")
export class <컨트롤러 이름> {
...
}
이후 /swagger
에 접속하면 Swagger UI를 확인할 수 있다.