Chomu's Blog.

>

Posts

GitHub

[포스코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-validatorclass-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를 확인할 수 있다.