logo
Published on

Nestjs 공식 문서를 읽어보자 - 1. Controllers

Authors
  • avatar
    Name
    Taehwa Yoon
    Twitter

기본 구조

nest cli를 통해 프로젝트를 생성하면 아래처럼 생긴 몇가지 파일들이 생성된다.

src
- app.controller.spec.ts
- app.controller.ts
- app.module.ts
- app.service.ts
- main.ts

여기서 우선 controller가 무엇인지 알아보자.

Routing

controller는 client로 부터 특정 route로 요청을 받는 역할을 한다. nestjs의 controller는 class와 decorator로 구성되어 있는데 일반적인 형태는 아래와 같다.

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

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats'
  }
}

@Controller decorator의 인자로 받은 cats는 route prefix로 이를 통해 손쉽게 관련된 route들을 하나의 class내에 묶을 수 있다.

@Get decorator는 nestjs에게 특정 endpoint에 대한 request handler를 생성하도록 명령한다. 이 때 @Get decorator의 인자로 path를 넘겨주면 route prefix와 path가 결합된 route path에 대한 request handler가 생성된다. 예를 들어 위의 예제 코드에서 @Get decorator의 인자로 "info"를 넘겨주게 되면 해당 매서드는 /cats/info endpoint에 대한 request handler가 된다.

이 때 request handler 매서드는 자동적으로 status code 200과 정의한 매서드의 리턴값을 객체 형태로 리턴해주는데 이 매서드의 리턴값이 primitive type인 경우 값 자체를, object나 array인 경우에는 serialized된 값을 리턴하게 된다. 따라서 개발자가 직접 status code를 설정하거나 리턴값을 serialize하는 등의 작업없이 간단하게 결과값만을 리턴하는 매서드만 작성하면 된다.

만약 express를 사용하는 경우 매서드의 인자로 express의 response객체를 inject할 수 있는데 findAll(@Res() response)의 형태로 작성해주면 된다. 주의할 점은 이렇게 @Res decorator를 통해 response를 inject하게 되면 nestjs는 해당 핸들러를 library-specific mode로 처리하게 되며 response처리에 대한 책임이 개발자에게 넘어간다. 즉, standard mode에서 nestjs가 자체적으로 처리해주던 status code나 serializing같은 부분을 사용자가 직접 response.status(200).send() 형태로 핸들링을 해주어야 한다.

Request object

@Req decorator를 통해 request object에 접근할 수 있다.

import { Controller, Get, Req } from '@nestjs/common'
import { Request } from 'express'

@Controller('cats')
export class CatsController {
  @Get()
  findAll(@Req() request: Request): string {
    return 'This action returns all cats'
  }
}

여기서 request의 type으로 express의 Request type을 사용했는데 이를 통해 express가 제공하는 typing을 활용할 수 있다.

request object는 HTTP request의 정보를 가진 객체이며 query string, parameters, HTTP headers, body등의 속성을 갖는다. nestjs에서는 이러한 property에 직접 접근하는 대신 @Param, @Body, @Query, @Headers 같은 built-in decorator들을 이용한다.

Resources

앞서 나온 예제와 더불어 일반적으로 resource를 생성하는(POST) endpoint를 작성해야 할 경우가 많다. nestjs를 이용한 post handler는 아래처럼 정의한다.

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

@Controller('cats')
export class CatsController {
  @Post()
  create(): string {
    return 'This action adds a new cat'
  }

  @Get()
  findAll(): string {
    return 'This action returns all cats'
  }
}

매우 간단하다. nestjs는 모든 HTTP method에 대한 decorator를 제공한다. 그리고 @All decorator를 통해 모든 method에 대응하는 handler를 만들 수도 있다.

Route wildcards

pattern형태의 route도 지원한다. +, *, ?는 각각의 정규표현식에 대응되는 형태로 활용할 수 있다. 다만 ., -는 문자 그대로 인식한다.

Status code

status코드는 default값 200으로 설정된다. 단, POST요청의 경우 201로 설정되며 만약 status code를 직접 수정할 경우 @HttpCode(204) 같은 형태로 decorator를 활용하면 된다.

Redirection

@Redirect('https://nestjs.com', 301)과 같은 형태로 redirection 처리를 할 수 있다. 만약 runtime에 dynamic하게 redirection 주소를 변경하고자 한다면 아래와 같은 객체를 리턴해주면 된다.

{
  "url": string,
  "statusCode": number
}

리턴되는 객체의 property들은 @Rediret() decorator의 대응되는 parameter들을 덮어쓰게 된다.

Request payloads

@Body() decorator를 통해 POST 요청에 대한 parameter를 설정할 수 있다. 그런데 우선 DTO(Data Transfer Object) schema를 정의해야 한다. DTO는 request에 어떤 형태의 data가 담겨서 보내질 지를 정의하는 객체이다. DTO는 typescript interface 또는 class로 정의할 수 있는데 nestjs 공식문서에서는 class로 정의하는 것을 추천한다. 왜냐하면 typescript interface는 transpile되어 사라지므로 runtime에 접근할 수 없는 반면, class는 runtime에 참조 가능하므로 nestjs가 제공하는 pipes같은 기능을 활용할 수 있기 때문이다.

느낀바

상당부분 java spring의 구조와 유사하다고 알고 있는데 사실 java spring을 써본적이 없어서 잘 모르겠지만 확실히 정해진 구조내에서 서비스로직만 구현하면 된다는 점이 우선 큰 장점으로 다가오는것 같다. 그리고 확실히 OOP에 대해서 공부를 하면 typescript를 활용하는 장점을 극대화 할 수 있을것 같다.

nestjs를 사용하지 않더라도 이와 유사한 model, service, controller 구조로 express를 활용해왔는데 사실 strict하게 layer를 지킨것 같지도 않고 중구난방이라는 느낌이 강했는데 어느정도 각 layer의 역할과 경계를 이해하는데 nestjs documentation이 많은 도움이 된것 같다.