본문 바로가기
Back-end/기초부터 따라하는 nest.js

시즌 2 #9. 기초부터 따라하는 Nest.js 2 : Pipe - 파이프

by hsloth 2024. 7. 2.

지난번 포스팅에서는 Nest.js에서 Query와 Param, Body를 다루는 법에 대해서 배웠습니다.

https://suloth.tistory.com/204

 

시즌 2 #8. 기초부터 따라하는 Nest.js 2 : Request 객체와 Query, Param, Body 사용법 (feat. Response)

지난 포스팅에서는 간단하게 API를 구현해봤습니다. 그리고 7-1를 보셨다면, 비동기란 무엇인지 아주 간략하게 봤습니다.https://suloth.tistory.com/202 시즌 2 #7. 기초부터 따라하는 Nest.js 2 : 간단한 API

suloth.tistory.com

 

이번 시간에는 파이프라는 것에 대해서 배워보려고 합니다.


과제 정답


 

과제 정답은 이렇습니다.

 

1. Query를 이용해서 Post Controller에서 Pagination이라는 것을 구현해볼 겁니다. Pagination이란 게시글 목록을 가져올 때, 전부 가져오지 않고 "페이지당 일정한 개수의 게시글만 가져오도록 하는 기법"입니다. 페이지네이션을 위해서는 기본적으로 "현재 페이지"와 "가져올 게시글 개수"가 필요합니다. 이 두 가지를 query로 받아서 그대로 출력하는 API를 만들어주세요. 경로는 /post/list로 하면 좋을 것 같네요.

ex) query를 a=1&b=2&c=3 으로 두었다면, 사용자는 

{ a : 1, b : 2, c : 3 }을 응답받게 됩니다.

 

정답

// post.controller.ts
// ...
  @Get('/list')
  async getPostList(
    @Query('perPage') perPage: number,
    @Query('curPage') curPage: number,
  ): Promise<any> {
    // 원래라면 이곳에 perPage와 curPage를 기반으로 게시글 목록을 뽑는 서비스 로직이 들어가야합니다.

    return {
      perPage,
      curPage,
    };
  }
// ...

 

그리고 브라우저에서 다음과 같이 url을 입력하면 결과를 볼 수 있습니다.

현재 사용자가 보고 있는 페이지는 1페이지이고, 해당 페이지에 담긴 게시글의 수는 10개라는 뜻입니다.

 

 

2. Param을 이용해서 N번째(id가 N인) 게시글을 가져오려고 합니다. 이것을 구현해주세요. 사용자는 게시글의 title과 content를 응답으로 받아야합니다.

// ...
  @Get('/:postId')
  async getPost(@Param('postId') postId: number): Promise<any> {
    return {
      title: `${postId} 번째 게시글입니다.`,
      content: `${postId} 번째 게시글의 내용입니다.`,
    };
  }
// ...

 

다음과 같이 주소창에 입력하면 결과 화면을 볼 수 있습니다.

 

3. Body를 이용해서 게시글을 작성하는 API를 만들어주세요. Body에 title과 content를 입력하면, 응답으로 title과 content를 그대로 리턴하도록 만들어주세요. (Post Controller에서 작업해주세요. 그리고 이때, 예전에 만들어두었던 getPostMainPage함수를 제거한 후에 만들어주세요. 해당 함수를 PostController에서 제거하지 않으면, Post데코레이터의 경로가 겹쳐서 제대로 동작하지 않을 수 있습니다.)

 

먼저, getPostMainPage함수를 제거해줍시다.

그리고 다음과 같이 함수를 작성해주세요.

// ...
  @Post()
  async createPost(@Body() body: any): Promise<any> {
    // 원래라면 이곳에 post를 생성하는 서비스 로직이 들어가야합니다.

    return {
      title: body.title,
      content: body.content,
    };
  }
// ...

 

이제 postman에 다음과 같이 입력하면 결과 화면이 나옵니다!

 

 


과제를 하면서


무언가 이상한 부분을 느끼셨나요?

만약 이상한 부분을 눈치채셨다면, 눈썰미가 좋으시군요!

 

그건 바로 1번, 2번 과제에서 Query와 Param에 대한 변수의 타입을 number로 설정하였는데 실직적으로는 string 타입이라는 겁니다.

 

다음과 같이 코드를 작성하고 해당 API를 실행해보면

위 처럼 string으로 뜹니다.... 분명 우리는 number타입으로 명시했는데 말이죠???

 

이유는 간단합니다. 원래 url을 통해 들어오는 모든 값들은 기본적으로 string타입으로 인식되기 때문입니다.

url = string이죠? 그러면 url안에 들어있는 query나 param도 당연히 string이겠죠?

그래서 우리는 이 string 값을 number로 바꿔줄 필요가 있습니다.

 


Pipe


string을 number로 바꿔주기 위해서는 이 pipe라는 녀석이 필요합니다.

pipe라는 녀석은 말그대로 파이프입니다. 파이프의 내부 구조는 모르지만, 무언가가 파이프를 거쳐서 나오면 조금 달라져있죠?

적절한 예를 들자면, 슈퍼마리오가 있을 수 있겠네요. 마리오가 녹색 관을 타고 들어가면 비밀스러운 장소로 이동할 수도 있지만, 아래 그림과 같이 이상한 괴물이 나타나서 마리오가 죽을수도 있습니다.

 

핵심은, 파이프 안에서 어떠한 일이 일어나는지는 아무도 모른다는 것입니다.

우리가 파이프를 사용할 때, 안에서 어떤 일이 일어나는지 모릅니다. (파이프가 투명한 파이프라면 알 수 있겠지만... 이런 예외는 넘어가도록합시다 ㅎㅎ) 결과만 알 뿐입니다.

 

물을 이 파이프로 보내면, A아파트에 도달할 거야!

가스를 이 파이프로 보내면, 유럽에 도달할 거야!

음료수를 빨대(파이프)로 보내면, 사람의 입에 도달할 거야!

 

처럼. 우리는 input와 output만 알면 됩니다.

 

그래서 우리는 string을 number로 바꾸기 위해, ParseIntPipe를 사용할겁니다.

이름만 들어도 감이 오죠? 값을 int로 바꿔주는 pipe입니다.

이녀석은 input(string)을 output(int)로 바꿔줍니다.

 

그래서 ParseIntPipe를 이용해서 1번의 코드를 수정해주면 다음과 같이 만들 수 있습니다.

// post.controller.ts
// ...
  @Get('/list')
  async getPostList(
    @Query('perPage', ParseIntPipe) perPage: number,
    @Query('curPage', new ParseIntPipe()) curPage: number,
  ): Promise<any> {
    // 원래라면 이곳에 perPage와 curPage를 기반으로 게시글 목록을 뽑는 서비스 로직이 들어가야합니다.

    console.log(typeof perPage);
    console.log(typeof curPage);

    return {
      perPage,
      curPage,
    };
  }
// ...

 

@Query의 두 번째 인자에 ParseIntPipe를 추가해주면 끝! 이죠.

해당 API를 실행시켜보면 이러한 결과를 얻을 수 있습니다.

 

왼쪽은 pipe 적용 전, 오른쪽은 pipe 적용 후

 

확실히 달라졌죠? 그리고 터미널에는 다음과 같이 뜰 겁니다.

 

그런데 위의 코드를 보면 이상한 점이 있습니다.

@Query에서 하나는 그냥 ParseIntPipe를 적어주었는데, 다른 하나의 query는 new ParseIntPipe()를 썼습니다.

 

파이프를 적용시키는 데에는 두 가지 방법이 있습니다.

  1. Nest.js에서 기본적으로 주입해주는 Pipe사용
  2. 사용자가 직접 커스텀하여 Pipe 사용

Nest.js에서 기본적으로 주입해주는 Pipe를 사용하는 것은 ParseIntPipe를 그대로 적어주는 것과 동일합니다.

ParseIntPipe만 적어주면, Nest.js에서 알아서 기본적으로 ParseIntPipe의 인스턴스를 생성해서 주입해줍니다. 이 방법이 쉽고 간단하죠.

 

new ParseIntPipe()는 사용자가 직접 커스텀하여 Pipe를 사용하는 방법입니다. 이 방법을 활용하면 파이프를 직접 커스텀해서 사용할 수 있습니다. ParseIntPipe의 인자로 다음과 같은 옵션들을 사용할 수 있습니다.

 

우리는 그냥 편하게 ParseIntPipe만 씁시다 ㅎㅎ

 

2번 과제 또한 ParseIntPipe를 추가해줍시다.

 


Pipe에 대한 추가 설명


pipe에는 두 가지 종류가 있습니다.

  1. 변환 파이프
  2. 유효성 검사 파이프

우리가 위에서 본 파이프는 "변환"에 해당하는 파이프입니다. 어떠한 값 혹은 타입을 다른 값 혹은 타입으로 변환시켜주는 녀석이죠.

 

유효성 검사 파이프는 Validation Pipe라고 부릅니다. 그리고 다른 말로는 Validator 라고도 부르죠.

말그대로 값의 유효성을 검사해줍니다.

Request의 data(query, param, body 등)의 타입이 일치하는지 검사해주는 역할도 하고요. 그 밖에도 직접적으로 함수의 인자, 리턴 타입 등이 실제 데이터와 일치하는지도 검사해줍니다.

이 부분에 대해서는 추후에 다른 포스팅에서 다루도록 하겠습니다.

 

일단, validation pipe라는게 있구나~ 하고 넘어갑시다.

 

 


과제


이번 과제는 Comment를 페이지네이션해서 가져오도록 하는 겁니다!

조금 복잡할 수도 있습니다.

 

우리가 저번 과제 2번에서 N번째(id가 N인) 게시글을 가져오는 API를 만들었잖아요? (위의 과제 정답 항목에서 getPost함수)

CommentService에서 comment list를 리턴하는 함수를 만들되, 페이지네이션으로 댓글을 페이지별로 일정 개수만큼 가져오도록 함수를 만들어봅시다.

그리고 CommentService에서 만든 함수를 PostController의 getPost함수에서 사용하도록 합시다.

즉, N번째 게시글을 가져오면 게시글 내용뿐만 아니라 게시글에 달린 댓글까지 가져오도록 만드는 겁니다.

이 때, 심심하니까 Query로 perCommentPagecurCommentPage를 사용해서 가져올 댓글 개수를 가져오도록 해봅시다. (원래는 이런식으로 코드를 작성하지는 않습니다...ㅎㅎ) ParseIntPipe 사용하는 것도 잊지 마시구요.

 

 

오늘도 제 글을 읽어주셔서 감사합니다. 다들 행복한 하루 보내시길 바랍니다.