Nestjs를 배워보자 5일차 - DTO, Route Parameter (John ahn님 강의)

2

Nestjs

목록 보기
5/9

Nest

본 강의는 'john ahn'님의 강의를 정리한 내용입니다.
https://www.youtube.com/watch?v=3JminDpCJNE

1. Data Transfer Object(DTO)

DTO는 무엇인가??
계층 간 데이터 교환을 위한 객체이다.

DB에서 데이터를 얻어 Service나 Controller 등으로 보낼 때 사용하는 객체를 말한다.

DTO는 데이터가 네트워크를 통해 전송되는 방법을 정의하는 객체이다. interface나, class를 이용해서 정의할 수 도 있다. nest에서는 클래스를 이용하는 것을 추천하고 있다.

좀 더 간단한 예시를 들어서 설명하자면, DTO는 하나의 디자인 패턴으로 데이터를 포맷(형식)화 하여 계층 간에 전송된다. 만약 A계층과 B계층이 데이터를 주고받는데, JSON 형식으로 만든 type C가 있다면 C는 DTO가 된다.

DTO를 사용하는 이유

  1. 데이터 유효성을 체크하는데 효율적이다.
  2. 더욱 안정적인 코드로 만들어준다. 타입스크립트의 타입으로도 사용된다.

우리가 만든 이전 코드들을 보도록 하자

constroller 부분을 보면

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

그런데 만약 createBoard 파라미터로 들어오는 값들이 사라지거나, 추가되는 경우 또는 변경되는 경우는 어떨까??

즉, createBoard의 파라미터로 description가 이제는 안쓰이는 경우는 어떻게 해줘야 하냐는 것이다.

우리는 controller의 파라미터인 description도 없애고 service메서드들 또한 바꿔야 할 것이다.

지금은 몇 가지 바꿀 것들이 많이 없지만 시간이 지날수록 많아지면서 일이 많아질 것이다.

DTO는 바로 이런 문제들을 해결해줄 수 있다. DTO하나를 정의해놓고 서로 다른 계층 또는 클래스 간에 데이터를 주고 받을 때, 우리가 주고 받는 Data(여기서는 파라미터)의 정의가 달라져도 상관없이 기존 코드를 이용할 수 있다는 것이다.

즉, DTO를 사용하면 Date(여기서는 파라미터) type의 정의를 변경하는 사이드 이펙트를 줄 일 수 있다는 것이다.

DTO 생성

게시물 생성을 위한 DTO를 만들어보자

src/boards/dto 폴더를 만들도록 하자
src/boards/dto/create-board.dto.ts 파일을 만들자

DTO는 interface나 class로 정의할 수 있는데, nest에서는 class를 권장한다. 왜냐하면 class는 인터페이스와 달리 런타임에서 작동하기 때문에 파이프 같은 기능을 이용할 때 유용하기 때문이다. 파이프 기능은 이후에 배우도록 하자

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

우리는 파라미터로 받을 데이터들인 titledescription을 DTO로 만들어 관리한다.

해당 DTO를 controller에 적용시키도록 하자

@Post()
createBoard(
    @Body() createBoardDto : CreateBoardDto 
) : Board {
    return this.boardService.createBoard(createBoardDto);  
}

이러면 service 쪽과 맞지않아서 에러가 발생할 것이다. serviece의 createBoard도 바꾸어주도록 하자

createBoard(createBoardDto : CreateBoardDto){
    const { title, description } = createBoardDto
    const board : Board = {
        title,
        description,
        id : uuid(),
        status : BoardStatus.PUBLIC,
    }
    this.boards.push(board);
    return board;
}

입력을 받는 부분을 모두 createBoardDto로 바꾸었다. 이게 무엇이 바뀌었는 지 아직은 크게 느껴지지 않는다.

그러나, 만약 description이 이제는 사용되지 않는다고 하자, 그러면 이전에는 파라미터에서 하나하나 찾아서 없애주어야 했는데, 지금은 createBoardDto에서 description만 없애주면 된다.

즉 controller와 service에 코드 변경이 없다는 것이다.
왜냐하면 createBoardDto가 @Body의 정보를 받을 때 JSON으로 받게되고 이를 createBoard() 함수의 파라미터에 넣어주게 된다.

이후에는 비구조할당이라는 js문법으로 const { title, description } = createBoardDto 데이터를 받을 수 있는데, 이는 createBoardDto안에 있는 프로퍼티(key)에 해당하는 이름은 값을 주지만, 없으면 주지 않는다.

즉, title은 아직있으니 값을 주지만, description은 삭제되었기 때문에 값을 주지 않아 undefined가 들어갈 것이다.

물론 쓸데없는 데이터가 들어간다는 단점이 있지만, DTO는 데이터를 주고받는 부분의 코드 변경을 줄인다는데 장점이 있는 디자인 패턴인 것이다.

2. 특정 ID를 이용해서 특정 게시물을 가져오는 기능 (Route Parameters)

특정 id의 게시물을 가져오기 위해서는 사용자로 부터 id를 받아오는 방법도 있지만, 보통 사용자는 id를 모르고 클릭하면 시스템 상에서 해당 id를 가져오는 방법을 사용한다.

이를 위해서 url에 parameter를 보내는데, nest가 이를 어떻게 처리하고 받을 수 있는 지 개발하면서 확인해보자

먼져 service부분으로가서 getBoardById 메서드를 만들어보자

getBoardById(id: string) : Board {
    return this.boards.find((board) => board.id === id)
}

전체 게시물에서 id가 맞는 부분만 골라내는 로직이다.

이제 controller에 해당 서비스를 적용시켜보자.
우리가 원하는 것은 url에 있는 parameter를 가져오는 것이다.

즉, 가령 localhost:3000?id=2 라고 했을 때 id param을 controller가 가져와서 이를 service에 넣어주어야 한다는 것이다.

controller에서 다음과 같이 사용하면 된다.

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

@Get('/:id')/:id를 통해서 parameter가 id인 경우를 말한다. 해당 id를 얻고자한다면 @Param() 데코레이터의 인자로 parameter를 입력하면 얻을 수 있다.

만약 parameter가 여러개가 온다면??

다음과 같은 경우이다.

localhost:3000?id=2&title=hello

와 같은 경우, 특정 파라미터만 controller에서 가져오는 방법은 위에서 이미했다. 그런데 파라미터 여러 개를 모두 가져오는 경우는 어떻게 해야할까?

아주 단순하게도 @Param() 데코레이터의 인자로 아무것도 안넣어주면 된다.

3. ID로 특정 게시물 지우기

특별히 위와 다를 바는 없다.

service 부분으로 가져 deleteBoard 함수를 만들도록 하자

deleteBoard(id : string){
    this.boards = this.boards.filter( (board) => board.id !== id)
}

filter 메서드를 이용하여 해당 id를 가진 board를 제외한 나머지만 남기고 this.board에 넣어주는 로직이다. delete이기 때문에 return 값이 없다.

이번에는 controller 부분을 변경하도록 하자, id가 필요하지만 get 메서드를 이용하진 않는다. 삭제이기 때문에 Delete 메서드를 이용하도록 하자

Delete 메서드는 @Delete() 로 사용하면 되고, 파라미터로 url parameter를 넣어주면 된다.

@Delete('/:id')
deleteBoard(@Param('id') id : string) : void {
    this.boardService.deleteBoard(id);
}

다음과 같이 정의하면 된다.

4. 특정 게시물의 상태 업데이트

특정 게시물의 STATUS 값을 변경하거나, 다른 프로퍼티를 변경하는 로직을 만들어보자

이를 위해서는 어떤 게시물(id)의 STATUS를 무엇( 새로운 STATUS값)으로 바꾸냐가 필요하므로, id와 STATUS 입력이 필요하다.

boardService에 다음의 메서드를 추가해주도록 하자

updateBoardStatus(id : string, status : BoardStatus) : Board{
    const board : Board = this.getBoardById(id);
    board.status = status;
    return board;
}

getBoardById() 메서드를 이용하여 해당 id의 board를 가져온다. 그리고 status를 입력으로 들어온 status로 바꾸어주고 return 시키도록 한다.

이제 Controller부분을 변경하도록 하자
새로 받아야할 데이터는 id와 status이므로, id와 status를 받아야 한다. 또한, http 메서드 중에서는 데이터를 변경하는 Patch() 메서드를 이용하도록 하자

Patch 역시도 데코레이터를 사용하면 된다. @Patch()로 사용하면 된다.
참고로, http 메서드로 post는 데이터를 생성할 때, 변경할 떄 사용한다. put과 patch 역시도 데이터를 변경하 때 사용되는데 간단하게 정리하면 다음과 같다.

post는 데이터를 변경하되, 절대 uri에 어떤 것의 어떤 값을 무엇으로 바꿀 지 나타나지 않는다. 오직 http body안에 숨겨서 전송하는 것이다.

put과 patch는 uri에 어떤 것을 바꿀지가 나오게 된다. 가령 우리의 예제의 경우에는 id가 된다. 다만 put은 데이터 전체를 바꾸고 patch는 데이터의 일부만 바꾼다. 따라서 우리는 status만 바꾸므로 patch를 쓴 것이다.

즉, post는 id를 노출하고 patch는 id를 노출하지 않을 것이다. 오로지 post는 하고자 하는 행위의 동사를 표현할 뿐이다. 정확히 말하자면 어차피 Body에 모두 숨기기 때문에 명사를 쓸 필요가 없다.

더 나아가서 이런저런 특징의 차이가 있긴 하지만, 이는 http 공부할 때로 미뤄두고 구현이나 해보자

@Patch('/:id/status')
updateBoardStatus(
    @Param('id') id : string,
    @Body('status') status : BoardStatus
){
    return this.boardService.updateBoardStatus(id, status);
}

다음과 같이 써주면 된다.

0개의 댓글