나만무 8일차 - NEST.js 공부

김명석·2025년 6월 26일

나만무

목록 보기
11/39

NestJS 기본 구조

nest-cli.json

nest 프로젝트를 위해 특정한 설정을 할 수 있는 json파일

tsconfig.json

어떻게 타입스크립트를 컴파일 할지 설정

tsconfig.build.json

tsconfig.json의 연장선 상 파일이면 빌드를 할때 필요한 설정들,
excludes에서는 빌드할 떄 필요없는 파일들 명시함

package.json

설치된 패키지들 목록 json

src 폴더

비즈니스 로직 들어가는 곳

main.ts

앱 생성 및 실행

app.module.ts

앱 모듈 정의

controller

nest g controller <만들고싶은컨트롤러이름> --no-spec

여기서 --no-spec은 테스트를 위한 코드 생성 명령어이다.
api의 입구 역할을 한다.

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

위 코드에서

constructor(private readonly appService: AppService)

AppService에 import로 서비스가 주입되어있기 때문에 자동으로 여기에 의존성 주입이 가능하다.

service

nest g service <만들고싶은서비스이름> --no-spec

실제 일을 수행한다.

주로

  • 도메인 규칙 계싼 : 핵심 업무 로직
  • 트랜젝션 관리 : DB 여러 테이블 호출한다.
  • 데이터 계층 호출 : Redis 캐시 접근
  • 오케스트레이션 : 이벤트 큐를 순차 및 병렬 호출한다.
  • 공통 유틸 헬퍼 메서드 : 다른 서비스, 컨트롤러에서 자유롭게 주입받아 공유한다.

등등을 한다.

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

@Injectable 데코레이터를 이용해서 다른 컴포넌트에서 이 서비스를 사용할 수 있게 만들어준다.

서비스를 컨트롤러에서 사용하기 위해서는 종속성 주입을 해줘야하는데,
이 종속성 주입은 클래스의 Constructor 안에서 이루어진다.

constructor(private boardService1: BoardService) {}

@Module

@AppModule이 시작점
@Module 데코레이터로 주석이 달린 클래스이다.

모듈 생성할 때

nest g module <만들고싶은모듈이름>

<>는 제거
로 모듈을 생성하면 된다.

저 명령으로 모듈을 생성하면 자동으로 모듈이 등록이 된다.

Controller Service Entity Repository DTO는 직접 만들어야한다.

Provider

provider는 종속성을 @Injectable로 넣어주는 것이다.
등록을 위해서는 moduleprovider로 넣어주면 가능하다.

Model

데이터가 어떤 형식으로 삽입되는지 그 모델을 만들어준다.

NestJS에서는 enum 타입을 정의해서 사용할 수 있다.

getAllBoards(): Board[] {
    return this.boards;
  }

여기서 : Board[] 부분은 리턴값으로 리턴의 형식을 정해준다.

게시물의 id는 uuid 모듈을 이용해서 유니크하게 만들어줄 수 있다.

npm install uuid --save

위 명령어를 이용해 유니크하게 만들어준다.

Post 형식

@Post('/create')
  createBoard(
    @Body('title') title: string,
    @Body('description') description: string,
  ): Board {
    return this.boardService.createBoard(title, description);
  }

NestJS에서는 위와 같은 방법으로 POST에 값을 넘긴다.
@Body를 이용해 가져온다.

DTO

Data Transfer Object
DB에서 데이터를 얻어 Service나 Controller 등으로 보낼때 쓴다.

데이터 유효성을 체크하고 안정적인 코드를 만드는데 좋다.

export class CreateBoardDto {
  title: string;
  description: string;
}

위와 같이 DTO를 만들고 실제로 컨트롤러와 서비스를

  @Post('/create')
  createBoard(@Body() createBoardDto: CreateBoardDto): Board {
    return this.boardService.createBoard(createBoardDto);
  }
createBoard(createBoardDto: CreateBoardDto) {
    const { title, description } = createBoardDto;
    const board: Board = {
      id: uuid(),
      title: title,
      description: description,
      status: BoardStatus.PUBLIC,
    };

    this.boards.push(board);
    return board;
  }

이와 같이 DTO를 적용해준다.

get으로 정보 가져오기

@Get(':id')
  getBoardById(@Param('id') id: string): Board | undefined {
    return this.boardService.getBoardById(id);
  }

:파라미터와 같이 get에 필요한 파라미터 정보를 가져올 수 있다.
Import Param이 필요하다.

그러면

http://localhost:3000/board?id=00506e80-525e-11f0-928b-dd1da14ba969

Delete로 삭제하기

@Delete(':id')
  deleteBoard(@Param('id') id: string): boolean {
    return this.boardService.deleteBoardById(id);
  }

Controller는 이와 같이 준비한다.

deleteBoardById(id: string): boolean {
    if (this.getBoardById(id)) {
      this.boards = this.boards.filter((board) => board.id !== id);
      return true;
    } else {
      return false;
    }
  }

filter는 해당 조건문에 맞는것으로 배열을 다시 만드는것이다.

Patch로 업데이트 하기

updateBoardStateById(id: string, status: BoardStatus): Board | undefined {
    const board = this.getBoardById(id);
    if (board) {
      board.status = status;
      return board;
    }
  }
@Patch('/:id/status')
  updateBoardStatus(
    @Param('id') id: string,
    @Body('status') status: BoardStatus,
  ) {
    return this.boardService.updateBoardStateById(id, status);
  }

undefined 오류 처리를 위해 if처리를 해줘야한다.

Pipe

파이프는 @Injectable() 데코레이터로 주석이 달린 클래스
데이터 전달과 데이터 확인을 위해 사용된다.

Data Transforation

입력 데이터를 원하는 형식으로 변환

ex) 숫자를 받길 원하는데 문자열 형식으로 온다면 파이프에서 숫자로 바꿔준다.

Data validation

입력 데이터를 평가하고 유효한 경우 변경되지 않은 상태로 전달한다.

ex) 이름 10자 제한에서 10자 이상이면 에러

파이프 사용법

handler level : 모든 파라미터에 적용되는 파이프

@UsePipes(pipe)

파라미터 레벨 : 특정한 인자에만 파라미터를 적용한다.

글로벌 파이프 : 클라이언트에서 들어오는 모든 요청에 대해 적용된다.

npm install class-validator class-transformer --save

위 명령어를 이용해 필요한 모듈을 설치한다.

DB 연결

TypeORM

TypeORM은 객체와 데이터베이스의 변형을 유연하게 사용할 수 있게 해주는 작업으로

쿼리문을 그대로 사용하지않고 쉽게 코드로 옮길 수 있다는 장점이 있다.

모델을 기반으로 테이블을 자동으로 생성한다.
개체를 쉽게 삽입, 업데이트 삭제가 가능하다.
테이블간의 매핑을 만든다.

npm install pg typeorm @nestjs/typeorm --save

위 명령어를 이용해 필수 모듈을 설치해준다.

사전 세팅

config
/* eslint-disable prettier/prettier */
import { TypeOrmModuleOptions } from '@nestjs/typeorm';

export const typeORMConfig: TypeOrmModuleOptions = {
  type: 'mysql',
  host: 'localhost',
  port: 3306,
  username: '유저이름',
  password: '비밀번호',
  database: '데이터베이스',
  entities: [__dirname + '/../**/*.entity.{js,ts}'],
  synchronize: true,
};

entities: [__dirname + '/../**/*.entity.{js,ts}']
위와 같은 설정으로 .enetity.{js, ts}로 마지막이 정의되어있는 entity를 모두 구별할 수 있다.

synchronize는 테이블을 생성할지 안할지 정의하는 것이다.

우리는 mysql을 쓰니 위와같이 일단 맞춰준다.

Entity

Entity는 코드 레벨에서 DB 테이블을 묘사하고 조작하기 위한 설계도이다.

import { BaseEntity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { BoardStatus } from './board.model';

export class Board extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  description: string;

  @Column()
  status: BoardStatus;
}

위와 같이 @Column으로 DB 칼럼을 정의한다.

Repository

Insert, Find, Delete 등등의 데이터베이스에 관련된 일은 리포지토리에서 한다.

import { DataSource } from 'typeorm';
import { Board } from '../board.entity';
import { Inject } from '@nestjs/common';

export const boardProvider = [
  {
    provide: 'BOARD_REPOSITORY',
    useFactory: (dataSource: DataSource) => dataSource.getRepository(Board),
    inject: ['DATA_SOURCE'],
  },
];

원래는 @EntityRepository(Board)를 이용했지만 지금은 위와 같은 방법으로 provider를 이용해 repository를 정의해야한다.
이를 쓰기 위해선

import { Module } from '@nestjs/common';
import { BoardController } from './board.controller';
import { BoardService } from './board.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Board } from './board.entity';
import { databaseProviders } from 'src/database/database.provider';
import { boardProvider } from './dto/board.provider';

@Module({
  imports: [TypeOrmModule.forFeature([Board])],
  providers: [...databaseProviders, BoardService, ...boardProvider],
  exports: [...databaseProviders, ...boardProvider],
  controllers: [BoardController],
})
export class BoardModule {}

위와 같이 모듈에 provider를 등록하고 사용하여야한다.

  constructor(
    @Inject('BOARD_REPOSITORY')
    private boardRepository: Repository<Board>,
  ) {} 

생성한 레포지토리는 constructor를 이용해 service에 주입해준다.

findOne은 해당 메소드의 하나의 요소를 가져온다
async await을 이용해 데이터베이스 작업이 끝난 후 결과값을 받을 수 있게 한다.

보드 만들기

repository에 있는 create 메소드를 사용해 만들 수 있다.

async createBoard(createBoardDto: CreateBoardDto) {
    const { title, description } = createBoardDto;
    const board = this.boardRepository.create({
      title: title,
      description: description,
      status: BoardStatus.PUBLIC,
    });
    await this.boardRepository.save(board);
    return board;
  }

create 메소드로 만들고 save로 해당 DB에 저장할 수 있다.
데이터베이스 관련 로직들은 다 리포지토리로 이동시키면 된다.

import { DataSource } from 'typeorm';
import { Board } from '../board.entity';
import { Inject } from '@nestjs/common';
import { BoardStatus } from '../board-status.enum';
import { CreateBoardDto } from './create_board.dto';

export const boardProvider = [
  {
    provide: 'BOARD_REPOSITORY',
    useFactory: (dataSource: DataSource) =>
      dataSource.getRepository(Board).extend({
        async createBoard(createBoardDto: CreateBoardDto): Promise<Board> {
          const { title, description } = createBoardDto;
          const board = this.create({
            title: title,
            description: description,
            status: BoardStatus.PUBLIC,
          });
          await this.save(board);
          return board;
        },
      }),
    inject: ['DATA_SOURCE'],
  },
];

커스텀 레포지토리에 함수를 만들기 위해선 extend({}) 안에 원하는 메소드 코드를 삽입하면 된다.

useFactory: (dataSource: DataSource) =>
      dataSource.getRepository(Board).extend

삭제도 비슷하게 구현 가능하다.

deleteBoardById(id: string): void {
    const result = this.boardRepository.delete(id);
    console.log('result', result);
  }
profile
응애

0개의 댓글