Controllers

이연중·2021년 7월 21일
0

NestJS

목록 보기
2/22
post-custom-banner

클라이언트로부터 들어오는 요청을 처리하고 응답을 클라이언트에 반환

라우팅 매커니즘에 따라 요청에 따라 적합한 컨트롤러에 매핑된다.

각 컨트롤러에는 각기 다른 작업을 수행하는 메서드가 하나 이상있다.

기본 컨트롤러를 만들기 위해 클래스와 데코레이터(@)를 사용하며, 데코레이터는 클래스를 필수 메타데이터와 연결하고, nest가 라우팅 맵을 만들 수 있도록 함(요청을 해당 컨트롤러에 연결)

Routing


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

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

@Controller() 데코레이터 안 path에는 cats라는 접두사를 지정함으로써 관련 라우트 집합을 쉽게 그룹화하고 반복코드를 최소화했다.

@Get() 데코레이터에 의해 nest는 GET /cats 요청을 해당 핸들러에 매핑한다.

이러한 과정을 거쳐 findAll() 메서드를 실행하고 상태코드 200과 문자열을 반환한다. 이유는?

nest의 응답을 조작하기 위한 두가지 옵션

Standard요청 핸들러가 자바스크립트 객체, 배열을 반환할 때 자동으로 JSON으로 직렬화된다. 하지만, 자바스크립트 Primitive type을 반환하면 nest는 직렬화를 하지 않고 값만 보낸다. 이로인해 응답처리가 간단해진다.
또한, 응답의 상태 코드는 201을 사용하는 POST 요청을 제외하고는 기본적으로 200이다. @HttpCode(...) 데코레이터를 추가해 상태코드를 변경할 수 있다.
Library-Specificmethod handler signiture(ex: findAll(@Res() response))에서 @Res() 데코레이터를 사용해 삽입할 수 있는 라이브러리별(ex: Express) 응답객체를 사용할 수 있음. 예를 들어 Express에서는 response.status(200).send()와 같은 코드를 사용해 응답 구성

※주의※

핸들러가 @Res(), @Next()(둘다 optional)를 동시에 사용하려는 경우에는 nest가 자동으로 Standard 방식을 비활성화시켜버린다. 이를 방지하기 위해서는 Res({passthrough: true}) passthrough 옵션을 true로 두면된다.

Request Object


핸들러에서 클라이언트 요청 객체(ex: req.body)에 엑세스 해야할 경우, nest에서 제공하는 기능을 이용하면 된다.

핸들러의 시그니처에 @Req() 데코레이터를 추가함으로써 nest에게 injection 하도록 지시하면, 요청 객체에 엑세스할 수 있다.

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';
  }
}

HTTP 요청 관련 데코레이터별 의미

@Request(), @Req()req
@Response(), @Res()*res
@Next()next
@Session()req.session
@Param(key?: string)req.params / req.params[key]
@Body(key?: string)req.body / req.body[key]
@Query(key?: string)req.query / req.query[key]
@Headers(name?: string)req.headers / req.headers[name]
@Ip()req.ip
@HostParam()req.hosts

기본 HTTP 플랫폼(Express, Fastify)에서 입력과의 호환성을 위해 nest는 위와 같은 데코레이터를 제공하며, 이를 사용하는 경우 기본 라이브러리(ex: @types/express)에 대한 타입도 가져와야 최대한으로 활용할 수 있다.

또한, @Res() 또는 @Response()를 핸들러에 삽입시 해당 핸들러에 대해 nest를 Library-Specify 모드로 설정하고 응답을 관리해야 한다.

Route wildcards


패턴 기반 라우트도 지원한다.

@Get('ab*cd')
findAll() {
  return 'This route uses a wildcard';
}

"abcd", "ab_cd", "abecd "등의 라우트 경로가 모두 포함된다. "?", "+", "*", "()"는 라우트 경로에 사용될 수 있다.

"_", "."은 문자열 기반 경로로 문자 그대로 해석된다.

Status Code


핸들러 레벨에서 @HttpCode(...) 데코레이터를 추가해 상태코드를 변경할 수 있다.

@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat';
}

Headers


커스텀 응답헤더를 지정하기 위해 @Header() 데코레이터나 Library-Specific 응답객체를 사용할 수 있다.

@Post()
@Header('Cache-Control', 'none')
create() {
  return 'This action adds a new cat';
}

Redirection


응답을 특정 URL로 redirection하기 위해 @Redirect() 데코레이터 또는 Library-Specific 응답객체를 사용할 수 있다.

@Redirect() 는 url과 statusCode라는 두개의 인수를 가진다. 둘 다 optional이며, 생략된 경우 statusCode의 default는 302(Found)이다.

@Get()
@Redirect('https://nestjs.com', 301)

HTTP 상태코드, redirection URL을 동적으로 지정하고싶으면, route handler method에서 다음과 같이 객체를 반환하면 된다

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

반환값에 URL이나 statusCode를 재정의 하게 되면, @Redirect() 데코레이터에 전달된 모든 인수가 덮어씌워진다

@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
  if (version && version === '5') {
    return { url: 'https://docs.nestjs.com/v5/' };
  }
}

위 예의 경우, URL이 반환 값으로 바뀌게 된다.

Route parameters


라우트 경로에 있는 Query Parameter 값을 가져올 수도 있다.

@Get(':id')
findOne(@Param() params): string {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}
@Get(':id')
findOne(@Param('id') id: string): string {
  return `This action returns a #${id} cat`;
}

Sub-Domain Routing


@Controller 데코레이터는 들어오는 요청의 HTTP 호스트가 특정값과 일치여부 확인을 위해 host 옵션을 사용할 수 있다.

@Controller({ host: 'admin.example.com' })
export class AdminController {
  @Get()
  index(): string {
    return 'Admin page';
  }
}

host 값을 다음과 같이 데코레이터로 받아와 메소드에서 사용할 수도 있다.

@Controller({ host: ':account.example.com' })
export class AccountController {
  @Get()
  getInfo(@HostParam('account') account: string) {
    return account;
  }
}

Asynchronicity


@Get()
async findAll(): Promise<any[]> {
  return [];
}
@Get()
findAll(): Observable<any[]> {
  return of([]);
}

위 두가지 방식은 같은 결과를 생성하며, 모든 비동기 함수는 Promise를 반환해야 하기에 위와 같은 방식들로 구현해야 한다.

Reqeust payloads


이전 POST 라우터에서는 클라이언트 매개변수가 없었다. 여기에 @Body() 데코레이터를 추가해 매개변수를 넣어보겠다.

그러기 전, DTO 스키마를 결정해야 한다. DTO는 데이터가 네트워크를 통해 전송되는 방식을 정의하는 객체이다.

TypeScript 인터페이스를 사용하거나 간단한 클래스를 사용해 DTO 스키마를 정의할 수 있다.(사실 DTO만으로는 검증이 완전히 되지 않는다. validator를 이용하여 보완을 해야한다.)

다만, 클래스는 ES6 표준의 일부이므로 컴파일된 JS에서 실제 엔터티로 유지되기에 클래스를 사용하는 것이 좋다.

반면, TypeScript는 인터페이스는 트랜스파일링 중에(TypeScript -> JavaScript) 제거되기 때문에 nest는 런타임에 이를 참조할 수 없다.(이러한 부분은 Pipe에서 활용된다. 나중에 확인)

export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  return 'This action adds a new cat';
}

Full resource sample


여러 데코레이터를 사용해 기본 컨트롤러를 만드는 예제이다.

import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common';
import { CreateCatDto, UpdateCatDto, ListAllEntities } from './dto';

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

  @Get()
  findAll(@Query() query: ListAllEntities) {
    return `This action returns all cats (limit: ${query.limit} items)`;
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return `This action returns a #${id} cat`;
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
    return `This action updates a #${id} cat`;
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return `This action removes a #${id} cat`;
  }
}

Getting up and Running


위처럼 컨트롤러가 정의되었다. 하지만, 아직 nest는 CatsController의 존재여부를 아직 모르므로 해당 클래스의 인스턴스를 만들지 않았다.

컨트롤러는 항상 모듈에 속하므로 @Module() 데코레이터 내에 controllers 배열을 포함한다.

import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';

@Module({
  controllers: [CatsController],
})
export class AppModule {}

이렇게 컨트롤러를 추가해주었으니 이제 nest에서는 CatsController의 인스턴스를 생성할 수 있을 것이다.

Library-specific approach


여태가지는 Standard 방식으로 응답을 조작했다.

Library-Specific 방식으로 CatsController를 재정의 해보겠다.

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

@Controller('cats')
export class CatsController {
  @Post()
  create(@Res() res: Response) {
    res.status(HttpStatus.CREATED).send();
  }

  @Get()
  findAll(@Res() res: Response) {
     res.status(HttpStatus.OK).json({success: true});
  }
}

위와 같이 하면 될 것이다. 하지만 이 방식에는 단점이 있다.

코드가 플랫폼에 종속되고, 테스트하기가 더 어려워진다.

또한 위의 예처럼 인터셉터 및 @HttpCode()/@Header() 데코레이터와 같은 nest standard 응답처리에 의존하는 nest 기능과의 호환성이 손실된다.

이 문제를 해결하기 위해서는 아까 초반에 설명했듯 아래와 같이 passthrough 옵션을 true 설정하면 된다.

@Get()
findAll(@Res({ passthrough: true }) res: Response) {
  res.status(HttpStatus.OK);
  return [];
}

참고

https://docs.nestjs.kr/controllers

profile
Always's Archives
post-custom-banner

0개의 댓글