티스토리 뷰
배경
nestjs를 예전부터 공부해보고 싶었다. 미뤄두고 있었는데 잠깐 시간이 나서 주말동안 nestjs를 공부해봤다. 그리고 무엇보다 새로운 프로젝트를 만들것인데 써보고 싶은 기술은 모두 써야 후회가 없을 것 같다. 괜찮은 유튜브 강의가 보이길래 그걸 보고 정리해봤다.
nestjs 시작하기
nestjs 설치
먼저 전역으로 nestjs cli를 설치해주자
npm i -g @nestjs/cli
그리고 다음 명령어를 통해 보일러 플레이트가 세팅된 프로젝트를 만들 수 있다.
nest new project-name
새로운 폴더를 만들지 않고 지금 경로에 만들고 싶다면 다음 명령어를 입력하자
new new ./
nestjs cli
다음 명령어로 module, controller, service를 설치할 수 있다. 모듈, 컨트롤러, 서비스 설명은 생략
nest g module boards
nest g controller boards --no-spec
nest g service boards --no-spec
// (no spec은 테스트 제외)
provider
provider : 서비스, 레포지토리 등이 provider다. 특징은 종속성으로 주입할 수 있다는 것이다.
의존성 주입을 모른다면 https://ms3864.tistory.com/410 참고
controller 사용법
- get ,post, delete, patch 데코레이터로 감싸주면 라우터가 된다.
- params, body, query 데코레이터를 이용해서 간편하게 가져올 수도 있다. 인자에 아무것도 넣지 않으면 객체로 가져오고 인자를 넣으면 그 인자를 가져온다.
- 서비스계층은 특별히 nestjs라고 다른점이 크게 없기 때문에 (inject제외) 설명 생략
@Controller('boards')
export class BoardsController {
constructor(private readonly boardsService: BoardsService) {}
@Get('/')
getAllBoards(): Board[] {
return this.boardsService.getAllBoards();
}
@Post('/')
createBoard(@Body() createBoardDto: CreateBoardDto): Board {
return this.boardsService.createBoard(createBoardDto);
}
@Get('/:id')
getBoardById(@Param('id') id: string): Board {
return this.boardsService.getBoardById(id);
}
@Delete('/:id')
deleteBoard(@Param('id') id: string): void {
this.boardsService.deleteBoard(id);
}
@Patch('/:id/status')
updateBoardStatus(@Param() id: string, @Body() status: BoardStatus) {
return this.updateBoardStatus(id, status);
}
}
dto
계층 간 데이터 교환을 하기 위해 사용하는 객체이다. 넘겨주는 값이 많아지게 되면 코드를 수정할 때도 힘들고 버그가 나기도 쉽다. dto를 만들어주자
pipe
pipe는 data transformation, data validation을 위해서 사용된다.
pipe level
- handler level pipe
@Post('/')
@UsePipes(ValidationPipe)
createBoard(@Body() createBoardDto: CreateBoardDto): Board {
return this.boardsService.createBoard(createBoardDto);
}
- parameter level pipe
@Post('/a')
createBoard(@Body('title', ParameterPype) title)
- global level pipe
app.useGlobalFilters(GlobalPype)
built in pipes
이미 만들어져있는 파이프들이 있다.
@Post('/')
@UsePipes(ValidationPipe)
createBoard(@Body() createBoardDto: CreateBoardDto): Board {
return this.boardsService.createBoard(createBoardDto);
}
- validation pipe
- parseint pipe
- parsebool pipe
- parse array pipe
- parse uuid pipe
- defaultvalue pipe
커스텀 파이프
PiepeTransform을 implement해야한다.
그리고 transform에서 유효성 검증이나 값을 변환해준다.
pipe
import { BadRequestException, PipeTransform } from '@nestjs/common';
import { BoardStatus } from '../board.model';
export class BoardStatusValidationPipe implements PipeTransform {
readonly StatusOptions = [BoardStatus.PRIVATE, BoardStatus.PUBLIC];
transform(value: any) {
value = value.toUpperCase();
if (!this.isStatusValid(value)) {
throw new BadRequestException(`${value} isn't in the status options`);
}
return value;
}
private isStatusValid(status: any) {
return this.StatusOptions.includes(status);
}
}
controller
@Patch('/:id/status')
updateBoardStatus(
@Param('id') id: string,
@Body('status', BoardStatusValidationPipe) status: BoardStatus,
) {
return this.boardsService.updateBoardStatus(id, status);
}
Exception
nest에서는 Exception이 굉장히 여러가지가 있다.
두가지의 인자를 전달할 수 있는데 아무것도 전달하지 않으면 statusCode, message를 return한다.
첫번째 인자만 전달하면 message값이 생겨나고 기존에 있던 message는 error가 된다.
두번째 인자도 전달하면 그 값이 error값이 된다.
ex)
인자 전달 x
{
"statusCode": 400,
"message": "Bad Request"
}
첫번째 인자만 전달
{
"statusCode": 400,
"message": "PU2BLIC isn't in the status options",
"error": "Bad Request"
}
첫번째, 두번째 인자 전달
{
"statusCode": 400,
"message": "PU2BLIC isn't in the status options",
"error": "public or private"
}
typeorm
다음 명령어로 typeorm을 설치하자. 2022.4.24기준 0.2버전을 설치해야한다. 이런건 날짜에 따라 바뀌기 때문에 공식문서를 찾아보자
yarn add @nestjs/typeorm
yarn add typeorm@0.2
typeorm에 대한 기본 설명은 생략한다. 이건 typeorm글이 아니기 때문에... 모른다면 https://ms3864.tistory.com/390 를 참조
간단한 예시 코드만 적어두겠다.
entity
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { BoardStatus } from './board-status.enum';
@Entity()
export class Board extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
description: string;
@Column()
status: BoardStatus;
}
repository
import { EntityRepository, Repository } from 'typeorm';
import { BoardStatus } from './board-status.enum';
import { Board } from './board.entity';
import { CreateBoardDto } from './dto/create-board.dto';
@EntityRepository(Board)
export class BoardRepository extends Repository<Board> {
async createBoard(createBoardDto: CreateBoardDto): Promise<Board> {
const { title, description } = createBoardDto;
const board = this.create({
title,
description,
status: BoardStatus.PUBLIC,
});
await this.save(board);
return board;
}
}
jwt
나중에 써야지...
미들웨어 순서
middleware -> guard -> interceptor(before) -> pipe -> controller -> service -> controller -> interceptor(after) -> filter(if applicable) -> client
log
express에서는 winston으로 로그를 찍었는데 nest에는 이미 내장되어있다.
log - 중요한 정보의 범용 로깅
warning - 치명적이거나 파괴적이지 않은 처리되지 않은 문제
error - 치명적이거나 파괴적인 처리되지 않은 문제
debug - 오류 발생시 로직을 디버그하는 데 도움이되는 유용한 정보(개발자용)
verbose - 응용 프로그램의 동작에 대한 통찰력을 제공하는 정보(운영자용)
config
보통 나는 dotenv를 설치해서 했는데 nestjs에서 만든것이 있다. dotenv를 내부에서 사용했다고 하던데.. 그냥 dotenv를 이용해도 상관없지만 그래도 한번 따라해봤다.
다음명령어로 설치하고
yarn add @nestjs/config
app.module에 이렇게 적용하면 된다.
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [ConfigModule.forRoot()],
})
export class AppModule {}
설정이 몇개있는데 공식문서를 읽어보자. 나는 전역상태와 env파일 분기처리만 해줬다.
ConfigModule.forRoot({
isGlobal: true,
envFilePath: `.env.${process.env.NODE_ENV}`,
}),
https://docs.nestjs.com/techniques/configuration
후기
기본적인 것만 해봤는데 나쁘지 않다. 그리고 기존에도 express로 계층을 나눠서 사용했는데 크게 다른점이 없었다. 익숙하지 않아서 조금 불편하기는 하지만 적응만 되면 훨씬 좋을 것 같다. throw error부분도 되어있어서 훨씬 편하다. express로 프로젝트를 만들면서 고민하고 적용했던 것들이 대부분 이미 구현되어 있었다.
글 제목이 nestjs 시작하기라서 정말 입문자분들이 이 글을 볼수도 있을텐데 처음부터 백엔드를 nestjs로 시작하는 건 별로인 것 같다.(내가 입문자들이 이해할 수 있을정도로 글을 친절하게 쓰지도 않았다.) 백엔드로 기본적인 계층이나 에러처리 등을 할 수 있을 때 nestjs로 넘어가는 걸 추천한다. 그래도 공부하고 싶다면 아래 영상을 보자. 나름 친절하게 준다. 그런데 이걸 정말 초심자가 이해할 수 있을지는 모르겠다.
참조영상
https://www.youtube.com/watch?v=3JminDpCJNE&ab_channel=JohnAhn