NestJS프레임워크의 개념에 대해 공부한 부분을 정리하여 포스트해보도록 하겠습니다.
이 글은 제목에서 확인할 수 있듯이 NestJS + TypeORM + MySQL의 조합 스택에 대한 기본개념에 대해 정리한 부분을 설명한 것이며 게시판 기능과 인증 기능의 예제를 만들어보며 실습할 것입니다.
NestJS는 Node.js 기반의 서버 사이드 애플리케이션을 구축하기 위한 프레임워크로, 모듈 아키텍처와 의존성 주입을 기반으로 강력한 코드 구조를 제공합니다.
NestJS를 사용하기 위해서는 @nestjs/cli를 전역으로 설치해야 합니다. 아래의 명령어로 Nest CLI를 설치해봅시다.
npm install -g @nestjs/cli
nest new 프로젝트명
프로젝트를 생성하면 다음과 같은 기본 파일구조를 가지고 있습니다.
project-root/
├── src/
│ ├── main.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
├── test/
├── .gitignore
├── package.json
└── tsconfig.json
이제 프로젝트의 기본 구조를 간략히 살펴봅시다.
다음은 NestJs의 진입점인 main.ts파일과 애플리케이션의 루트모듈인 app.module.ts파일을 코드와 함께 자세하게 살펴보겠습니다.
app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { BoardsModule } from './boards/boards.module'; // 필요한 모듈을 임포트하세요
// 모듈 데코레이터 (모듈 정의)
@Module({
imports: [
BoardsModule, // 필요한 모듈을 추가
],
controllers: [AppController], // 컨트롤러를 여기에 등록
providers: [AppService], // 서비스를 여기에 등록
})
export class AppModule {}
모듈에선 의존성을 관리하며 기능을 구현할 때 모듈을 계속 다루게 될 것입니다.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 유효성 검사 파이프를 전역으로 사용합니다.
app.useGlobalPipes(new ValidationPipe());
// 서버를 시작합니다.
await app.listen(3000);
}
bootstrap();
NestJS의 애플리케이션 로직은 다음과 같은 흐름을 따릅니다
이제 위의 개념들을 기반으로 "Hello Nest.js!"라는 문자열을 반환하는 기본적인 Nest.js 애플리케이션이 구현해 보겠습니다.
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
provides: [AppService],
})
export class AppModule {}
// app.controller.ts
import { Contoller, Get } from '@nestjs/common';
import { AppService } from './app.service';
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello()
}
}
// app.service.ts
import { Injectable } from '@nestjs/common';
export class AppService {
getHello(): string {
return 'Hello Nest.js!';
}
}
nestjs의 주요 요소인 모듈, 컨트롤러, 서비스에 대해 알아보고 각 요소의 예시코드를 보겠습니다.
@Module 데코레이터가 달린 클래스로, 관련된 컨트롤러, 서비스 및 기타 프로바이더들을 하나로 묶는 역할을 합니다. 모듈은 계층적인 구조로 구성되며, 의존성 주입 및 코드의 모듈화를 통해 애플리케이션의 확장성과 유지 보수성을 향상시킵니다.
// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
@Controller 데코레이터가 달린 클래스로, 컨트롤러는 클라이언트의 요청을 처리하고, 서비스로부터 데이터를 받는 역할을 합니다. 컨트롤러는 라우팅 역할을 수행하며, 특정 엔드포인트와 HTTP 메서드에 대한 처리 로직을 정의합니다.
// cats.constoller.ts
import { Controller } from '@nestjs/common';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get()
async findAllCats(): Promise<any[]> {
this.catsService.findAllCats();
}
}
@Injectable 데코레이터가 달린 클래스로, 서비스는 비즈니스 로직을 담당하며, 컨트롤러로부터 전달받은 요청을 처리하고 데이터베이스나 외부 API와의 상호작용을 수행합니다.
// cats.service.ts
import { Injectable } from '@nestjs/common'
@Injectable()
export class CatsService {
private readonly cats: any[] = []
async findAllCats(): Promise<any[]> {
return this.cats;
}
}
먼저, 게시판 관련 모듈을 생성해보겠습니다.
nest generate module boards
위 명령어를 실행하면 src/boards 폴더와 그 안에 boards.module.ts 파일이 생성됩니다.
boards.module.ts 파일에서는 @Module 데코레이터를 사용하여 해당 모듈의 구성 요소들을 정의합니다.
// src/boards/boards.module.ts
import { Module } from '@nestjs/common';
import { BoardsController } from './boards.controller'
import { BoardsService } from './boards.service'
@Module({
controllers: [BoardsController],
providers: [BoardsService],
})
export class BoardsModule {}
다음으로, 게시판 컨트롤러를 생성합니다.
nest generate controller boards --no-spec
위 명령어를 실행하면 src/boards 폴더 안에 boards.controller.ts 파일이 생성됩니다.
또한, boards.module.ts 파일에 BoardsController가 자동으로 임포트됩니다.
boards.controller.ts 파일에는 @Controller 데코레이터를 이용해 엔드포인트와 메서드를 정의합니다.
// src/boards/boards.controller.ts
import { Controller, Get } from '@nestjs/common';
import { BoardsService } from './boards.service';
@Controller('board')
export class BoardsController {
// 의존성주입
constructor(private readonly boardsService: BoardsService) {}
// 엔드포인트 메서드
@Get()
getAllBoards(): string {
return this.boardsService.getAllBoards();
}
}
마지막으로, 게시판 서비스를 생성합니다.
nest generate service boards --no-spec
위 명령어를 실행하면 src/boards 폴더 안에 boards.service.ts 파일이 생성됩니다.
또한, boards.module.ts 파일에 BoardsService가 자동으로 임포트됩니다.
boards.service.ts 파일에는 @Injectable 데코레이터를 이용해 서비스의 메서드들을 구현합니다.
// src/boards/boards.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class BoardsService {
// 비즈니스 로직 메서드들
private readonly boards = ['Board 1', 'Board 2', 'Board 3']
getAllBoards(): string[] {
return this.boards;
}
}
서비스를 컨트롤러로 주입하기 위해서는 의존성 주입을 사용합니다. 위의 코드에서 보듯이, BoardsController에서 BoardsService를 생성자 주입으로 받습니다.
이렇게 하면, BoardsController 내에서 BoardsService의 메서드를 호출할 수 있습니다.
잠시 의존성 주입에 대해 간략하게 알아보겠습니다.
의존성 주입: 클래스의 생성자(constructor)를 활용한 의존성 주입은 객체 간의 관계를 느슨하게 결합하고 코드의 유지 보수성을 높이는 데 사용되는 디자인 패턴입니다.
의존성 주입은 클래스가 필요로 하는 의존하는 객체(또는 클래스)를 외부에서 제공하도록 하는 패턴입니다.
// 의존성 주입할 인터페이스
interface Logger {
log(message: string): void;
}
// Logger 인터페이스를 구현한 클래스
class ConsoleLogger implements Logger {
log(message: string): void {
console.log(`[Log] ${message}`);
}
}
// 서비스 클래스, 생성자 주입을 활용한 예시
class MyService {
private logger: Logger;
constructor(logger: Logger) {
this.logger = logger;
}
doSomething(): void {
this.logger.log("Doing something...");
}
}
// 의존성 주입된 객체를 생성하여 서비스 사용
const logger = new ConsoleLogger();
const service = new MyService(logger);
service.doSomething(); // 출력: [Log] Doing something...
우선, 게시판 CRUD를 위한 서비스, 컨트롤러, 모듈을 구현하기 전에 사용할 부가적인 요소들에 대해 정리해보겠습니다.
// ./src/boards/boards.model.ts
export interface Board {
id: string;
title: string;
description: string;
status: BoardStatus;
}
export enum BoardStatus {
PUBLIC = 'PUBLIC',
PRIVATE = 'PRIVATE',
}
모델을 통해 데이터의 구조를 나타내기 위해 인터페이스나 클래스를 생성합니다. 이는 데이터의 필드와 타입을 정의하는 역할을 합니다.
NestJS에서 Model은 애플리케이션의 데이터 구조와 상호 작용을 관리하는 중요한 부분입니다.
이 후 typeOrm과 MySQL을 조합해 나가며 Model은 Entity로 대체될 것입니다.
또한, Enum(이넘)으로 상수 값들의 집합을 정의하였습니다.
// ./src/boards/create-board.dto.ts
import { IsNotEmpty } from 'class-validator';
export class CreateBoardDto {
@IsNotEmpty
title: string;
@IsNotEmpty
description: string;
}
CreateBoardDto는 게시물 생성 시 사용되는 데이터 전송 객체(DTO)입니다.
DTO는 비즈니스 로직과 분리된 상태로 데이터를 전송하고 받는 역할을 수행합니다. 이를 통해 데이터 구조와 필드를 명확하게 정의하고 관리할 수 있습니다.
import { Controller, Post, Body } from '@nestjs/common';
import { CreateBoardDto } from './create-board.dto';
@Controller('boards')
export class BoardsController {
// ...
@Post('/')
createBoard(@Body() createBoardDto: CreateBoardDto) {
// createBoardDto.title, createBoardDto.description 사용
// 서비스 메서드 호출 등 비즈니스 로직 처리
}
}
위와 같이 Controller측에서 @Body에서 받는 데이터를 DTO가 전송해줍니다.
import { Injectable } from '@nestjs/common';
import { CreateBoardDto } from './create-board.dto';
import { Board } from './board.model';
@Injectable()
export class BoardsService {
// ...
createBoard(createBoardDto: CreateBoardDto): Board {
const { title, description } = createBoardDto;
// 데이터 가공 및 저장 로직 수행
// ...
}
}
그리고 위와 같이 Service측에서 DTO를 수신하여 필요한 데이터를 구조분해할당 해주어 사용합니다.
우선, 파이프를 사용하기 위해 다음과 같이 설치해줍니다.
npm install class-validator class-transformer --save
ValidationPipe를 핸들러 레벨에서 사용하기 위해 @UsePipes(ValidationPipe) 데코레이터를 추가하였습니다.
이로인해 해당 Post메서드를 동작할 땐 유효성검사를 실행하게 됩니다.
// ./src/board/boards.controller.ts
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common';
import { CreateBoardDto } from './CreateBoardDto';
@Controller('boards')
export class BoardsController {
@Post()
@UsePipes(ValidationPipe)
createBoard(@Body() createBoardDto: CreateBoardDto) {
// 유효성 검사를 통과한 데이터 처리
}
}
CreateBoardDto에는 유효성 검사를 위한 데코레이터를 적용하였습니다.
// ./src/boards/CreateBoardDto.ts
import { IsNotEmpty } from 'class-validator';
export class CreateBoardDto {
@IsNotEmpty()
title: string;
@IsNotEmpty()
description: string;
}
데이터가 유효성 검사를 통과하지 않으면 NestJS는 자동으로 예외(에러메시지)를 생성하고 반환합니다.
또한, NotFoundException과 같은 예외 클래스를 활용하여 간편하게 예외와 에러를 처리할 수 있습니다.
// ./src/boards/boards.service.ts
import { NotFoundException } from '@nestjs/common';
// ...
getBoardById(id: string): Board {
const found = this.boards.find(board => board.id === id);
if (!found) {
throw new NotFoundException("해당 ID의 게시물을 찾을 수 없습니다.");
}
return found;
}
// ...
NotFoundException은 404 상태 코드와 함께 예외를 생성합니다. 이로써 클라이언트에게 "해당 ID의 게시물을 찾을 수 없습니다."라는 메시지와 함께 404 응답이 전달됩니다.
유효성 체크 커스텀 파이프 만들어보기
커스텀 파이프를 생성하여 직접 본인이 원하는 유효성검사를 하는 코드를 작성할 수도 있습니다.
// board/pipes/board-status-validation.pipe.ts
import { PipeTransform, BadRequestException } from '@nestjs/common';
import { BoardStatus } from '../board.model';
export class BoardStatusValidationPipe implements PipeTransform {
private readonly statusOptions = [
BoardStatus.PRIVATE,
BoardStatus.PUBLIC,
];
transform(value: any) {
value = value.toUpperCase();
if (!this.isStatusValid(value)) {
throw new BadRequestException(`"${value}" is an invalid status`);
}
return value;
}
private isStatusValid(status: string) {
const index = this.statusOptions.indexOf(status);
return index !== -1;
}
}
아래와 같이 콘트롤러 핸들러에서 커스텀 파이프를 사용하여 유효성 검사를 수행하여 입력 데이터를 사전에 검증할 수 있습니다.
@Post('/:id/status')
updateBoardStatus(
@Param('id') id: string,
@Body('status', BoardStatusValidationPipe) status: string,
): Board {
return this.boardsService.updateBoardStatus(id, status);
}
파이프(Pipe)는 NestJS 애플리케이션에서 들어오는 데이터를 검증, 변환 및 가공하기 위한 중간 레벨의 컴포넌트입니다. 파이프는 주로 컨트롤러 메서드의 매개변수나 반환값을 가공하거나 검증하는데 사용됩니다.
파이프를 정의하는 방법은 세가지로 구분할 수 있습니다.
// 예시
import { Controller, Get, UsePipes } from '@nestjs/common';
import { CustomPipe } from './custom.pipe';
@Controller('example')
export class ExampleController {
@Get()
@UsePipes(CustomPipe)
getData() {
// 커스텀 파이프(CustomPipe)가 여기서 적용됨
}
}
// 예시
import { Controller, Get, Query, Param } from '@nestjs/common';
import { ParseIntPipe } from '@nestjs/common';
@Controller('example')
export class ExampleController {
@Get(':id')
getById(@Param('id', ParseIntPipe) id: number) {
// ParseIntPipe가 여기서 적용되어 id 매개변수를 정수로 변환
}
@Get()
getData(@Query('page', ParseIntPipe) page: number) {
// ParseIntPipe가 여기서 적용되어 page 매개변수를 정수로 변환
}
}
// 예시
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new CustomPipe()); // 글로벌 파이프 설정
await app.listen(3000);
}
bootstrap();
이와 같이 class-validator와 class-transformer를 사용하여 데이터의 유효성을 검사하고 원하는 형식으로 변환할 수 있습니다. 이것은 데이터의 품질과 안정성을 확보하고, 코드를 보다 깔끔하게 유지하는 데 도움이 됩니다.
// src/boards/boards.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { Board } from './board.model';
import { v1 as uuid } from 'uuid';
import { CreatBoardDto } from './create-board/dto';
@Injectable()
export class BoardsService {
private boards: Board[] = [];
// 모든 값 가져오기: GET
getAllBoards(): Board[] {
return this.boards;
}
// 게시물 생성하기: POST
createBoard(createBoardDto: CreateBoardDto): Board {
const { title, description } = createBoardDto: CreateBoardDto;
const board: Board = {
id: uuid(),
title,
description,
status: BoardStatus.PUBLIC,
};
this.boards.push(board);
return board;
}
// 특정 게시물 가져오기: GET
getBoardById(id: string): Board {
const found = this.boards.find((board) => board.id === id);
if(!found) {
throw new NotFoundException(`Board with ID ${id} not found`);
}
return found
}
// 특정 게시물 지우기: DELETE
deleteBoard(id: string): void {
this.boards = this.boards.filter((board) => board.id !== id);
}
// 특정 게시물 status값 업데이트: PATCH
updateBoardStatus(id: string, status: BoardStatus): Board {
const board = this.getBoardById(id);
board.status = status;
return board;
}
}
uuid 패키지를 사용하여 게시물의 고유 ID를 생성하였습니다.
// ./src/board/board.controller
import { Controller, Get, Post, Body, Param, Delete, Patch, UsePipe, ValidationPipe } from '@nestjs/common';
import { BoardsService } from './boards.service';
import { Board } from './board.model';
import { BoardStatus } from './board.model';
import { BoardStatusValidationPipe } from './pipes/board-status-validation.pipe'
@Controller('boards')
export class BoardsController {
constructor(private readonly boardsService: BoardsService) {}
// 모든 값 가져오기: GET
@Get('/')
getAllBoards(): Board[] {
return this.boardsService.getAllBoards();
}
// 게시물 생성하기: POST
@Post()
@UsePipes(ValidationPipe)
createBoard(@Body createBoardDto: CreateBoardDto): Board {
return this.boardsService.createBoard(createBoardDto);
}
// 특정 게시물 가져오기: GET
@Get('/:id')
getBoardById(@Param('id') id: string): Board {
return this.boardsService.getBoardById(id);
}
// 특정 게시물 지우기: DELETE
@Delete('/:id')
deleteBoard(@Param('id') id: string): void {
this.boardsService.deleteBoard(id);
}
// 특정 게시물 status값 업데이트: PATCH
@Patch('/:id/status')
updateBoard(
@Param('id') id: string,
@Body('status', BoardStatusValidationPipe) status: BoardStatus
): Board {
return this.boardsService.updateBoardStatus(id, status);
}
}
TypeORM은 TypeScript에서 관계형 데이터베이스와 작업을 수행하는 데 사용되는 ORM(Object-Relational Mapping) 프레임워크입니다.
타입ORM을 사용하기 위해 다음과 같이 모듈을 설치하고 설정합니다.
npm i mysql2 typeorm @nestjs/typeorm --save
타입ORM의 설정은 다음과 같이 typeorm.config.ts 파일을 생성하여 데이터베이스의 설정정보를 담을 수 있습니다.
아래와 같은 설정정보를 코드베이스에 그대로 남겨두는 것은 보안상 좋지 않으므로 이후에 환경변수를 사용하여 재설정할 것입니다.
// ./src/configs/typeorm.config.ts
import { TypeOrmModuleOptions } from '@nestjs/common';
export const typeORMConfig: TypeOrmModuleOptions = {
type: 'mysql',
host: 'localhost',
port: 5432,
username: 'yourusername',
password: 'yourpassword',
database: 'yourdatabase',
entities: [__dirname + '/../**/*.entity.{js,ts}'],
};
이제 애플리케이션에 타입ORM을 적용해보겠습니다. 루트 모듈에서 타입ORM을 설정해줍니다.
// ./src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { BoardModule } from './board/board.module';
import { typeORMConfig } from './configs/typeorm.config';
@Module({
imports: [TypeOrmModule.forRoot(typeORMConfig), BoardModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
엔티티(Entity)는 데이터베이스에서 특정 개체나 개념을 표현하는 데 사용되는 클래스입니다.
엔티티 클래스는 데이터베이스 레코드와 상호작용하기 위한 메서드와 속성을 정의합니다.
// src/boards/board.entity.ts
import { BaseEntity, PrimaryGeneratedColumn, Column, Entity } from 'typeorm';
import { BoardStatus } from './board.model';
@Entity()
export class Board extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
description: string;
@Column()
status: BoardStatus;
}
타입ORM의 엔티티 클래스에서 사용된 Type 종류는 다음과 같습니다.
이후에 릴레이션을 정의하는 Type 종류를 사용할 것입니다.
Repository는 엔티티에 대한 CRUD(Create, Read, Update, Delete) 작업을 제공하는 클래스입니다.
Repository Pattern은 데이터베이스 작업을 서비스로부터 분리하여 더 모듈화된 코드를 작성할 수 있도록 도와줍니다.
// ./src/boards/boards.repository.ts
import { Board } from './boards.entity';
import { Repository, DataSource } from 'typeorm';
import { Injectable } from '@nestjs/common';
@Injectable()
export class BoardsRepository extends Repository<Board> {
constructor(private dataSource: DataSource) {
super(Board, dataSource.createEntityManager());
}
//...
}
// ./src/boards/boards.service.ts
import { BoardsRepository } from './boards.repository';
import { Inject } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
@Injectable()
export class BoardsService {
constructor(private boardsRepository: BoardsRepository) {}
//...
}
// src/boards/boards.module.ts
import { Module } from '@nestjs/common'
import { TypeormModule } from '@nestjs/typeorm'
import { BoardsController } from './boards.controller'
import { BoardsService } from './boards.service'
import { BoardsRepository } from './boards.repository'
import { Board } from './board.entity'
@Module({
imports: [
TypeOrmModule.forFeature([Board])
],
controllers: [BoardsController],
provides: [BoardsService, BoardsRepository],
exports: [BoardService],
})
export class BoardsModule {}
지금까지 게시판 CRUD를 로컬 메모리로 작업했던 것을, typeorm과 데이터베이스연결을 통해 데이터베이스 CRUD로 작업이 가능하게 되었습니다.
레포지토리, 서비스, 컨트롤러, 모듈을 작성해보겠습니다.
import { Controller, Get, Param, Post, Body, Delete, Patch, UsePipes, ParseIntPipe } from '@nestjs/common';
import { BoardsService } from './boards.service';
import { CreateBoardDto } from './create-board.dto';
import { BoardStatusValidationPipe } from './board-status-validation.pipe';
import { Board } from './board.entity';
@Controller('boards')
export class BoardsController {
// 의존성 주입
constructor(private readonly boardsService: BoardsService) {}
// 모든 게시물 가져오기
@Get()
getAllBoards(): Promise<Board[]> {
return this.boardsService.getAllBoards();
}
// 특정 id값을 가진 게시물 가져오기
@Get('/:id')
getBoardById(@Param('id', ParseIntPipe) id: number): Promise<Board> {
return this.boardsService.getBoardById(id);
}
// 게시물 생성하기
@Post()
@UsePipes(ValidationPipe)
createBoard(@Body() createBoardDto: CreateBoardDto): Promise<Board> {
return this.boardsService.createBoard(createBoardDto);
}
// 게시물 삭제하기
@Delete('/:id')
deleteBoard(@Param('id', ParseIntPipe) id: number): Promise<void> {
return this.boardsService.deleteBoard(id);
}
// 게시물 상태값 업데이트하기
@Patch('/:id')
updateBoardStatus(
@Param('id', ParseIntPipe) id: number,
@Body('status', BoardStatusValidationPipe) status: BoardStatus
): Promise<Board> {
return this.boardsService.updateBoardStatus(id, status);
}
}
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Board } from './board.entity';
import { CreateBoardDto } from './create-board.dto';
import { BoardsRepository } from './boards.repository';
import { BoardStatus } from './board-status.enum';
@Injectable()
export class BoardsService {
// 의존성 주입
constructor(private readonly boardsRepository: BoardRepository) {}
// 모든 게시물 가져오기
async getAllBoards(): Promise<Board[]> {
return this.boardsRepository.getAllBoards();
}
// 특정 id값을 가진 게시물 가져오기
async getBoardById(id: number): Promise<Board> {
return this.boardsRepository.getBoardById(id);
}
// 게시물 생성하기
async createBoard(createBoardDto: CreateBoardDto): Promise<Board> {
return this.boardsRepository.createBoard(createBoardDto);
}
// 게시물 삭제하기
async deleteBoard(id: number): Promise<void> {
return this.boardsRepository.deleteBoard(id);
}
// 게시물 업데이트하기
async updateBoardStatus(id: number, status: BoardStatus): Promise<Board> {
return this.boardsRepository.updateBoardStatus(id, status);
}
}
import { Repository, DataSource } from 'typeorm';
import { Board } from './board.entity';
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateBoardDto } from './create-board.dto';
import { BoardStatus } from './board-status.enum';
@Injectable(Board)
export class BoardRepository extends Repository<Board> {
constructor(private dataSource: DataSource){
super(Board, dataSource.createEntityManager())
}
// 모든 게시물 가져오기
async getAllBoards(): Promise<Board[]> {
const boards = await this.find();
return boards;
}
// 특정 id값을 가진 게시물 가져오기
async getBoardById(id: number): Promise<Board> {
const found = await this.findOne(id);
if (!found) {
throw new NotFoundException(`Board with ID ${id} not found`);
}
return found;
}
// 게시물 생성하기
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;
}
// 게시물 삭제하기
async deleteBoard(id: number): Promise<void> {
const result = await this.delete(id);
if (result.affected === 0) {
throw new NotFoundException(`Board with ID ${id} not found`);
}
}
// 게시물 상태값 업데이트하기
async updateBoardStatus(id: number, status: BoardStatus): Promise<Board> {
const board = await this.getBoardById(id);
board.status = status;
await this.save(board);
return board;
}
}
이 글에서는 NestJS와 TypeORM을 사용하여 간단한 게시판 서비스를 구축하는 방법에 대해 알아보았습니다.
블로그를 포스팅하기 전에 개념을 공부해보며 이전에 사용해 보았던 Express와는 확연히 다른 구조를 가지고 있다고 느꼈습니다.
Express는 높은 자유도와 미니멀함이라는 장점을 가지고 있지만 한편으론, 아키텍쳐에 대한 불명확성이 큰 단점으로 자리잡고 있습니다. 반면에 NestJs의 모듈 그리고 모듈과 밀접하게 연결되어있는 요소들 그리고 의존성주입을 사용함으로써 Express의 아키텍쳐에 대한 아쉬움을 해소할 수 있을 것 같다는 느낌이 들었습니다.
또한, 타입스크립트가 기본으로 내장되어 있으므로 타입정의가 용이 하다는점, 객체지향 프로그래밍의 장점(모듈 및 캡슐화, 상속 등)이 잘 녹아있다는 점 등이 좋았습니다.
이 블로그 글은 인프런 강의 따라하며 배우는 NestJS를 공부하며 이를 바탕으로 작성하였으며, nestjs의 버전 업그레이드로 인해 Repository의 정의 방식이 변경되었으므로 그에 맞게 작성하였습니다. (그 부분은 stackoverflow 참고하였음.)
해당강의는 NestJS의 개념을 잡기에 매우 좋은 강의이므로 입문자에게 추천합니다.
이상으로 마치겠습니다. 감사합니다.