Node.js에 기반한 웹 API 프레임워크로 Node의 과도한 유연함으로 인한 단점에 비해 많은 기본 기능을 제공함과 동시에 확장성을 지니고 있다.
IOC, DI, AOP와 같은 OOP개념을 도입하고 있으며, 기본적으로 typescipt를 채택하고 있다.
Node기반의 웹 AIP 프레임워크 Express를 래핑하여 동작한다.
npm i -g @nestjs/cli
nest new project-name
npm install // 필요한 패키지를 설치
npm run start // 서버 실행
npm run start:dev // 개발 모드 실행
dev모드로 실행하면 --watch모드로 실행되어 소스코드 변경시 서버 자동으로 재시작된다.
nest g module boards
아래의 코드에는 컨트롤러나 서비스 객체가 이미 등록되어 있는데, 각 컴포넌트를 생성한 후 직접 입력해줘야 한다.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'
import { BoardsController } from './boards.controller';
import { BoardsService } from './boards.service';
// import { Board } from './board.entity';
import { BoardRepository } from './board.repository';
import { Board } from './board.entity';
@Module({
imports: [
TypeOrmModule.forFeature([Board]) // 엔티티 등록
],
controllers: [BoardsController], // 컨트롤러 등록
providers: [BoardsService, BoardRepository] // Provider 등록
})
export class BoardsModule {}
객체가 의존하는 대상으로, nest에서 서비스, 리포지토리, 팩토리, 헬퍼 등이 프로바이더가 될 수 있다.
객체가 필요로하는 기능을 제공하는 대상(provider)으로 생각하면 될 것 같다.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm/dist';
import { BoardsModule } from './boards/boards.module';
import { typeORMConfig } from './configs/typeorm.config';
@Module({
// import해줄 module을 담아주면 됨
imports: [
BoardsModule
],
})
export class AppModule {}
nest g controller boards --no-spec
@Controller('boards')
export class BoardsController {
// 접근 제한자를 생성자 파라미터에 선언하면
// 접근 제한자가 사용된 생성자 파라미터는 암묵적으로 클래스 프로퍼티로 선언이 됨
// 원래는 스프링처럼 필드에 변수를 선언하고 생성자에서 변수에 주입을 받아야 함
constructor(private boardService: BoardsService){}
@Post()
@UsePipes(ValidationPipe)
createBoard(@Body() createBoard: CreateBoardDto) : Promise<Board>{
// console.log("createBoard");
return this.boardService.creatBoard(createBoard);
}
}
nest g service boards --no-spec
@Injectable()
export class BoardsService {
constructor(
private boardRepository: BoardRepository
){}
creatBoard(createBoardDto: CreateBoardDto): Promise<Board>{
return this.boardRepository.createBoard(createBoardDto);
}
여기까지 기본적인 형태에 대해 알아보았고, 아래에서 Pipe와 DB통신을 위한 TypeORM에 대해 알아보자.
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}는 유효하지 않은 상태입니다.`);
}
return value;
}
private isStatusValid(status:any){
const index = this.StatusOptions.indexOf(status);
return index !== -1;
}
}
데이터를 입력할 때 파라미터로 지정한 타입과 매칭되지 않는 타입의 데이터가 들어오면 500에러가 발생한다. 이 경우 그저 서버 에러가 되어버리기 때문에 클라이언트측에서 이를 알아채지 못하고 다시 같은 문제가 반복될 가능성이 있다.
이런 문제에 대해 nest에서 제공하는 class-validator를 활용하면 controller로 데이터가 전달되기 전에 입력 데이터에 대한 유효성 검사를 진행하고 400번대 에러와 메세지를 발생시킬 수 있다.
$ npm i --save class-validator class-transforme
class validator에서 제공하는 데코레이터는 관련 문서를 확인하자.
TypeORM은 node에서 실행되는 typescript기반의 ORM을 뜻한다. 스프링에는 JPA가 있다.
npm install pg typeorm @nestjs/typeorm --save
코드를 보면 pg, typeorm, @nestjs/typeorm를 설치하고 있는데, 각 모듈은 아래와 같다
typeorm: typeorm모듈
pg : Postgres모듈
@nestjs/typeorm: NestJs에서 TypeORM을 사용하기 위해 연동시켜주는 모듈
이고, TypeORM을 사용하기 위해 설치해줘야 한다. 이것도 공식 문서에 나와있다.
db에 접근하기 위해 config파일에 정보를 입력해주자.
import { TypeOrmModuleOptions } from "@nestjs/typeorm";
export const typeORMConfig: TypeOrmModuleOptions ={
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: '1234',
database: 'board-app',
entities: [__dirname + '/../**/*.entity.{js,ts}'], // 엔티티 등록
synchronize: true
}
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm/dist';
import { BoardsModule } from './boards/boards.module';
import { typeORMConfig } from './configs/typeorm.config';
@Module({
// import해줄 module을 담아주면 됨
imports: [
TypeOrmModule.forRoot(typeORMConfig), // typeorm 설정파일 등록
BoardsModule
],
})
export class AppModule {}
이제 TypeORM 사용을 위한 준비는 끝났다.
import {BaseEntity, PrimaryGeneratedColumn, Column, Entity} from 'typeorm'
import { BoardStatus } from './board-status.enum';
@Entity() // 테이블과 매핑하는 데코레이터, 속성값으로 테이블의 이름을 따로 지정할 수 있음
export class Board{
@PrimaryGeneratedColumn() // 기본키, 값 자동생성
id: number;
@Column()
title: string;
@Column()
description: string;
@Column()
status: BoardStatus;
}
자세한 설명은 Repository 생성에서 같이 한다.
이제 db관련 로직을 처리하기 위해 Repository를 생성해주자.
import {Injectable} from '@nestjs/common'
import {InjectRepository} from '@nestjs/typeorm'
import {Repository} from 'typeorm'
import { Board } from './board.entity';
import { CreateBoardDto } from './dto/create-board.dto';
import { BoardStatus } from './board-status.enum';
import { log } from 'console';
@Injectable()
export class BoardRepository{
constructor(
@InjectRepository(Board)
private readonly boardRepository: Repository<Board>
){}
async createBoard(createBoardDto: CreateBoardDto): Promise<Board>{
const{title, description} =createBoardDto;
const board = this.boardRepository.create({
title,
description,
status: BoardStatus.PUBLIC
})
await this.boardRepository.save(board);
return board;
}
typeorm에서 제공하는 api는 TypeORM 공식문서를 참고하자.
참고로 db에서 데이터를 지우는 메서드는 아래 두가지 방법이 있는데, 차이는 아래와 같다.
typeorm에는 BaseEntity 클래스를 활용한 ActiveRecord 패턴과, Repository<엔티티>객체를 활용한 DataMapper패턴이 있는데, 여기서는 DataMapper패턴을 사용하고 있다.
엔티티와 리포지토리를 Injectable 객체로 만들어줬으니 모듈에 등록해주자.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'
import { BoardsController } from './boards.controller';
import { BoardsService } from './boards.service';
import { BoardRepository } from './board.repository';
import { Board } from './board.entity';
@Module({
imports: [
TypeOrmModule.forFeature([Board]) // 엔티티 등록
],
controllers: [BoardsController],
providers: [BoardsService, BoardRepository] // 리포지토리 등록
})
export class BoardsModule {}
npm install uuid --save
보일러 플레이트 코드는 여기 참고