NestJS 帶你飛! 시리즈 번역 04# Controller (하)

2023. 5. 30. 16:13개발 문서 번역/NestJS

728x90
이 포스팅은 「NestJS 기초실무 가이드 : 강력하고 쉬운 Node.js 웹 프레임워크로 웹사이트 만들기」
(서명: NestJS 基礎必學實務指南:使用強大且易擴展的 Node.js 框架打造網頁應用程式)
책이 출간되었습니다. 

바디 (Body)

데이터를 전송할때는 보통 바디에 담아 자료를 전송하게 됩니다. (ex: POST, PUT, PATCH 등의 작업)

Nest는 @Body 데코레이터를 통해 바디의 데이터를 받아올 수 있습니다.

아래는 예제입니다.

import { Body, Controller, Post } from '@nestjs/common';

@Controller('todos')
export class TodoController {
  @Post()
  create(@Body() data: { title: string, description?: string }) {
    const id = 1;
    return { id, ...data };
  }
}

아래와 같이 파라미터를 지정하는 방식으로도 데이터를 받아올 수 있습니다.

import { Body, Controller, Post } from '@nestjs/common';

@Controller('todos')
export class TodoController {
  @Post()
  create(
    @Body('title') title: string,
    @Body('description') description?: string
  ) {
    const id = 1;
    return { id, title, description };
  }
}

Postman으로 불러온 결과입니다.

 

DTO 사용하기

DTO의 풀 네임은 Data Transfer Object로 정말 광범위한 용도로 사용되는 객체입니다.

데이터를 필터링 한다던가, 정해진 데이터 양식으로 맞춘다던지.. 등 말이죠.

DTO는 정보를 전달할 자료들만을 저장하게 되는데 이는 DTO가 '읽기 전용'(Readonly, 唯讀)속성임을 의미합니다.

DTO를 정의한 후에는 매개변수 형식에 구애받을 필요가 없어집니다. DTO를 사용함으로써 내가 전달하고자 하는 데이터가 어떻게 생겼고, 내가 반환하고자 하는 것에 대해 신경 쓸 필요가 없어집니다.

Nest에서 DTO의 양식에는 일종의 필터 같은걸 내장하여 사용할 수 있습니다. 이는 곧 유지보수 비용 감소로 이어지죠.

 

여타 프로그래밍 언어와 같이 JavaScript 진영에서도 이미 정의된 2가지의 방법이 있습니다.

1. TypeScript의 interface

2. 모던 자바스크립트가 지원하는 class

 

일반적으로는 class의 형식으로 DTO를 만들곤 합니다.
이유는 interface는 JavaScript로 컴파일되는 과정에서 삭제되기 때문이죠. 게다가 class는 당연하게도 삭제되지 않습니다.
이는 기능에도 영향을 끼치게됩니다. 따라서 특별한 상황이 아니라면 class를 통해 DTO를 정의하는걸 추천합니다.

 

그렇다면 DTO 예제를 하나 만들어보겠습니다. 해당 Controller의 폴더에서 dto라는 폴더를 하나 만들어줍니다.

이름은 create-<CONTROLLER_NAME>.dto.ts로 지으면 됩니다. 이 포스팅에서는 create-todo.dto.ts로 명명하겠습니다.

 

export class CreateTodoDto {
  public readonly title: string;
  public readonly description?: string;
}

DTO를 만든 후, Controller에서 사용하면 됩니다.
@Body 데코레이터의 파라미터 형식에 해당 DTO를 사용하면 됩니다.

아래는 예제입니다.

 

import { Body, Controller, Post } from '@nestjs/common';
import { CreateTodoDto } from './dto/create-todo.dto';

@Controller('todos')
export class TodoController {
  @Post()
  create(@Body() dto: CreateTodoDto) {
    const id = 1;
    return { id, ...dto };
  }
}

이렇게 이 todos 라는 경로에서 POST로 요청을 하면

최종적으로 해당 id(지금은 하드코딩 되어있습니다.)와 DTO에서 정의했던 항목들이 반환되게 됩니다

 

헤더 (Headers) 

개발을 하다보면 헤더를 설정해 프론트단에 값을 전달해주어야 할 때가 있습니다. 그럴때 @Header 데코레이터를 사용하면 됩니다.

import { Controller, Get, Header } from '@nestjs/common';

@Controller('todos')
export class TodoController {
  @Get()
  @Header('TaeHyeon-Headers', '1')
  getAll() {
    return {
      id: 1,
      title: 'Title 1',
      description: ''
    };
  }
}

아래는 Postman을 통해 요청한 결과입니다.

 

파라미터 데코레이터 (Parameter Decorator)

앞에서 Nest는 Express 혹은 Fastify위에서 돌아가는 웹 프레임 워크라고 언급한 적이 있습니다.

당연하게도 많은 부분이 아래에서 돌아가는 플랫폼에서 가져온 사항들이 많습니다.

이중에서 파라미터 데코레이터가 바로 그 좋은 예인데요.

정의된 파라미터 데코레이터로 각기 다른 정보를 받아올 수 있습니다. 앞에서 소개했던 몇개 말고도 Nest에서는 많은 파라미터 데코레이터를 제공하여 개발자에게 좀 더 편한 방법으로 다양한 데이터를 받아올 수 있게 하고있습니다.

 

  • @Request(): 요청 데코레이터입니다. 이 데코레이터는 기반이 되는 프레임워크의 요청 객체(Request Object)를 부여합니다. 별칭은 @Req()이며 일반적으로 파라미터의 이름은 req로 짓습니다.

  • @Response(): 응답 데코레이터입니다. 이 데코레이터는 기반이 되는 프레임워크의 응답 객체(Response Object)를 부여합니다. 별칭은 @Res()이며 일반적으로 파라미터의 이름은 res로 짓습니다.

  • @Next(): Next 함수의 데코레이터입니다. 이 데코레이터는 기반이 되는 프레임워크의 Nest 함수를 부여합니다. 주로 다음 미들웨어를 호출하는데 사용됩니다.
    자세한 설명은 예전에 작성한 Express 기본 구조와 라우팅(중문) 포스팅을 참고해주세요.

  • @Param(key?: string): URL 내 파라미터를 받아오는 데코레이터입니다.
    Express의 req.params 혹은 req.params[key]와 동일합니다.

  • @Query(key?: string): URL 내 쿼리를 받아오는 데코레이터입니다.
    Express의 req.query 혹은 req.query[key]와 동일합니다.

  • @Body(key?: string): 바디의 데이터를 받아오는 데코레이터입니다.
    Express의 req.body 혹은 req.body[key]와 동일합니다.

  • @Headers(name?: string): 헤더의 데이터를 받아오는 데코레이터입니다.
    Express의 req.headers 혹은 req.headers[name]와 동일합니다.

  • @Session(): 세션의 데코레이터입니다.
    Express의 req.session과 동일합니다.

  • @Ip(): IP의 데코레이터입니다.
    Express의 req.ip와 동일합니다.

  • @HostParam(): host의 데코레이터입니다.
    Express의 req.hosts와 동일합니다.

return 처리하기

앞에서 다룬 예제들로 return의 방법으로 데이터를 응답하는 방법에 대해 알아보겠습니다.

Nest는 두가지의 데이터 응답 처리 방법을 제공합니다.

 

표준

return을 통해 반환된 데이터를 Nest에게 반환하는 작업을 시키는 방법입니다.
Nest에서 가장 추천하는 방식이며, 예제 코드는 아래에 적어두었습니다.

import { Controller, Get } from '@nestjs/common';

@Controller('todos')
export class TodoController {
  @Get()
  getAll() {
    return [];
  }
}

 

비동기

백엔드의 영역을 공부하거나 실무를 하다보면 비동기 처리에 대해서 많이 들어보았을 것입니다.

ES7의 async/await을 통해 비동기 처리를 할 수 있고 표준 방식에서도 비동기 처리를 사용할 수 있습니다.

import { Controller, Get } from '@nestjs/common';

@Controller('todos')
export class TodoController {
  @Get()
  async getAll() {
    return new Promise((resolve, reject) => setTimeout(() => resolve([]), 1000));
  }
}

 

RxJS

RxJS는 최근에 들어 꽤나 유명해진 라이브러리입니다.
Angular 진영에서 이 RxJS를 자주 볼 수 있는데

Angular에서 영감을 받은 Nest도 이 RxJS를 채택하였습니다.
Nest는 자동으로 해당 구독 대상에 대해 구독/취소 작업이 처리되어 수동으로 이를 처리할 필요가 없습니다.
아래는 예제입니다.

(구독에 대해서는 RxJS 문서를 참고 해주세요.)

import { Controller, Get } from '@nestjs/common';
import { of } from 'rxjs';

@Controller('todos')
export class TodoController {
  @Get()
  getAll() {
    return of([]);
  }
}

라이브러리 모드

이 방법은 기반이 되는 프레임워크의 응답객체를 통해 반환하는 방식입니다.

return의 방식으로 반환하지 않으면 모든 반환값은 void가 됩니다. 아래는 예제입니다.

import { Controller, Get, Res } from '@nestjs/common';
import { Response } from 'express';

@Controller('todos')
export class TodoController {
  @Get()
  getAll(@Res() res: Response) {
    res.send([]);
  }
}
주의 : 기반이 되는 프레임워크에 따라 res의 타입을 결정해야 합니다.
예제에서 사용한 코드는 Express를 기반으로 하기때문에 Response 타입을 사용하였습니다.

 

패턴 제한

Nest는 @Res, @Response, @Next 데코레이터의 함수가 사용되는지 매번 검사합니다.

있다면 라이브러리 모드로 변경되며, 표준 모드는 쓸 수 없게됩니다.

간단하게 말하면 return값의 방식이 먹히지 않게 된다는 의미입니다. 아래에는 예제를 적어놓았습니다. 한번 살펴볼까요?

import { Controller, Get, Res } from '@nestjs/common';
import { Response } from 'express';

@Controller('todos')
export class TodoController {
  @Get()
  getAll(@Res() res: Response) {
    return [];
  }
}

해당 주소로 접속해보면 계속 요청을 보낼 뿐 응답을 받아오지 못하는 모습을 확인할 수 있습니다.

 

그렇다면 정말 응답 객체로부터 값을 받아오고 싶은데, 표준 모드로 불러오고 싶을때는 어떻게 해야할까요?

방법이 없는걸까요? 아닙니다. 정답은 데코레이터에 passthrough:true를 붙이면 해결할 수 있습니다.

import { Controller, Get, Res } from '@nestjs/common';
import { Response } from 'express';

@Controller('todos')
export class TodoController {
  @Get()
  getAll(@Res({ passthrough: true }) res: Response) {
    return [];
  }
}

원하지 않았던 무한로딩 대신 빈 객체가 정상적으로 반환 되었습니다.

 

 

마치며

오늘 배운 내용을 정리해봅시다.

 

1. @Body를 통해 바디에 담긴 데이터를 받아올 수 있습니다.

2. DTO를 통해 데이터를 정의하고 전송하는 양식을 사용해야 합니다.

3. @Header를 통해 헤더에게 데이터를 넘겨줄 수 있습니다.

4. Nest에서 제공하는 파라미터 데코레이터의 기능에 대해 알아보았습니다.

5. 표준 모드를 사용한다는것은 return의 방법으로 데이터를 반환하는 방식입니다. async/await, RxJS또한 지원합니다.

6. 라이브러리 모드에서는 기반이 되는 프레임워크의 응답 객체가 데이터를 반환합니다.

7. @Response, @Res, 혹은 @Next 데코레이터를 사용하게 되면 표준모드를 사용할 수 없게됩니다.

만약 사용하고자 한다면 데코레이터에 passthrough: true 옵션을 추가해주어 사용할 수 있습니다.

 

Controller는 프론트단의 요청을 처리를 담당한다고 했습니다.
API의 문이라고 생각하면 됩니다. 그렇다면 각기 다른 요청에는 적합한 응답이 있어야 하겠죠?
Controller의 기능은 딱 맞는 기능을 제공한다고 할 수 있겠습니다.

 

Controller는 어떤 구역의 웨이터라고 말했던거 기억 나시나요?

지금의 예제에서는 Controller를 전부 루트 모듈에 연결했었는데
만약 모든 Controller가 루트 모듈에 연결되어있다면 모든 웨이터들이 적재적소에 배치되지 못한것이라 할 수 있습니다.

레스토랑에 비유하면 좋은 동선을 짜는게 어려워집니다.

그렇기 때문에 Controller는 알맞는 Module에 import하여 사용해야 합니다.

다음 편에서는 Nest의 Module에 대해 다뤄보도록 하겠습니다.

내일 만나요!