NestJS 독학 - Controller

김정원·2020년 10월 18일
0

NestJS 독학

목록 보기
2/4

Controller

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

소개

NestJS의 Controller는 Express에서 Router와 비슷한 역할을 해준다. 엔드포인트 요청에 대한 처리를 조작해주는데, @Controller 데코레이터 클래스를 통해 Route될 엔드포인트를 설정해준다. 이후 constructor를 통해 Provider를 정의해 준다.

Controller의 메소드에도 각각 데코레이터를 달아 모든 HTTP Request(@Post(), @Put(), @Delete(), @Patch(), @Options(), @Head(), @All()) 요청에 대한 응답을 메소드를 통해 적절한 동작을 지정해 줄수 있다. 그 외에도 데코레이션을 추가해 Redirection, Header, HttpCode등의 부가적인 기능을 지정할 수도 있다.

또한 각 메소드의 인자에도 데코레이터를 통해 Request Object를 할당할 수 있으며 ParamiterQuery 등을 한번에 받아올 수도 있다.

라우팅

@Controller() 데코레이터를 통해 설정해 엔드포인트를 지정하여 라우팅 할 수 있으며 controller.ts 파일 안에 필수적으로 포함되어 있어야 한다. /cats 엔드포인트에 라우팅하는 예제를 통해 살펴보자.

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

@Controller('cats')
// '/cats/' 엔드포인트로 접속
export class CatsController {
  @Get()
	// HTTP GET 요청에 대한 응답을 지정
  findAll(): string {
    return 'This action returns all cats';
  }
}

$ nest g controller cats CLI 명령을 통해 생성해 줄 수도 있다.

/cats 엔드포인트에 get 요청에 대한 응답까지 지정해주는 코드이다. 클래스 선언과 메소드 선언부에 데코레이터를 이용해서 간단하게 지정해 줄 수 있다. typescript를 활용하기 위해서는 응답에 대한 타입을 지정해야 한다. 응답을 반환하는 방법은 두가지인데, 위의 예제처럼 메소드의 return 값을 통해 응답하거나, Express에서 사용하던 방법대로 res객체를 통해 응답하는 방법이 있다. NestJS에서는 기본적으로 메소드의 return을 통해 응답하는 방법을 추천한다. 이 방법을 사용하면 기본적으로 status code200으로 지정되며 POST요청시에 201을 사용한다. 두가지 방법을 혼용하는 것은 불가능하다.

HTTP Request에 대한 엔드포인트 지정도 가능하다. 만약 @Get요청에 대한 엔드포인트를 hello로 지정한다면, findAll 메소드는 /cats/hello 에서 GET 요청을 받는다.

요청 객체 (Request Object)

HTTP Request를 수행하기 위해서는 Request 객체에 접근해야 하는 경우가 생긴다. 이 경우에는 인자에 @Req(혹은 @Request) 데코레이터를 붙여 Request 객체를 지정해 사용할 수 있다. NestJS에서는 @Param, @BodyRequset의 내부 객체도 한번에 받아올 수 있는 데코레이터를 제공한다 물론 req객체에 직접 접근하여 사용할 수도 있다.

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

@Controller('cats')
export class CatsController {
  @Get()
  findAll(@Req() request: Request): string {
	//@Req() 데코레이터를 통해 request 객체를 인자로 가져온다.
	//인터페이스를 지정할 때는 express모듈에서 Request를 가져와 사용한다.
    return 'This action returns all cats';
  }
}

Request Object

여기서 @Response() 데코레이터를 통해 res객체를 불러올 경우 라우팅에서 설명한 NestJS의 응답방법은 사용이 불가능해지고 기존 Express에서 res객체를 조작하는 방식으로 작동하게 된다. 호환성을 위해 존재하는 옵션이니 일반적인 상황에서는 사용을 지양하는 것이 좋다.

Resources

기본적인 HTTP 메소드에 해당하는 데코레이터(@Post(), @Put(), @Delete(), @Patch(), @Options(), @Head(), @All())를 모두 클래스 메소드 위에 붙여 요청에 대한 작업을 지정해 줄 수 있으며 각 HTTP 의 요청 엔드포인트를 데코레이터를 호출할 때 지정해 줄 수도 있다.

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

@Controller('cats')
export class CatsController {
  @Post()
	//POST 요청에 대한 동작을 추가.
  create(): string {
    return 'This action adds a new cat';
  }

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

Route wildcards

모든 엔드포인트에는 와일드카드를 지정해 줄 수도 있다.

@Get('ab*cd')
//ab*cd에 대한 요청을 모두 수행.
findAll() {
  return 'This route uses a wildcard';
}

Status code

@HttpCode() 데코레이터를 추가해 요청에 대한 statud code를 임의로 지정해 줄수도 있다.

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

다만, 상황에 따라 다른 status code를 지정해 줘야할 때는 @Res() 데코레이터를 통해 res객체를 조작하거나 throw를 이용해 조작할 수 있다.

Headers

임의로 응답헤더를 지정해 줄 수도 있다.

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

Redirection

리다이렉션을 사용할 수도 있는데, 리다이렉션될 링크와 status code를 인수로 받는다. status code는 기본적으로 302로 지정된다. @Redirect 데코레이터의 인자를 동적으로 지정하려면 메소드의 return 값을 통해 동적으로 지정해 줄 수 도 있다.

{
    "url": string,
    "statusCode": number
}
@Get("docs")
@Redirect("https://docs.nestjs.com", 302)
//기본적인 Redirect 속성
getDocs(@Query("version") version) {
    if (version && version === "7") {
        return {url: "https://docs.nestjs.com/v5/"};
				//메소드를 통해 특정 조건에서 속성을 재설정할 수도 있다.
    }
}

Route parameters

엔드포인트를 통해 파라미터를 받아올수도 있다. :string형식으로 지정해 주면 해당 자리에 들어오는 값을 파라미터로 전달하여 params객체로 받아올 수 있다.

@Get(':id')
//id 파라미터를 받는다.
findOne(@Param() params): string {
// 데코레이터를 통해 파라미터 객체 지정
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}

Sub-Domain Routing

@Controller 데코레이터를 통해 서브도메인을 라우팅 해줄 수도 있다.

@Controller({ host: 'admin.example.com' })
// host 속성을 통해 서브도메인을 지정해준다.
export class AdminController {
  @Get()
  index(): string {
    return 'Admin page';
  }
}

파라미터를 통해 서브도메인 지정도 가능하다. 이 경우에는 @HostParam데코레이터를 통해 서브도메인 파라미터의 이름을 지정해 줘야 한다.

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

Asynchronicity

모든 메소드는 async/await 을 통해 비동기 처리를 할 수 있다. 대부분의 ORM은 비동기로 처리되기 때문에 자주 사용하게 된다. 비동기 메소드는 Promise객체를 반환한다.

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

RxJS를 사용하는 경우 Observable객체를 반환할 수도 있다.

Request payloads

@Post 데코레이터를 통해 POST요청을 받으면 함께 Body 객체도 받을 수 있는데, 먼저 @Body데코레이터를 통해 Body 객체를 지정해줄 수 있다. 그 전에 먼저 *.dts.ts 파일을 생성해 dts(Data Transfer Object)의 타입을 클래스로 정의한다. typescript의 인터페이스가 아니라 클래스로 지정하는 이유는, 인터페이스의 경우는 TSC에서만 작동하는 실체가 없는 객체이지만 클래스는 TSC와 상관없이 ES6에서 기본적으로 지원하는 자바스크립트 문법이기 때문에 컴파일 이후에도 계속 실체할 수 있다.

CreateCatDto 클래스를 정의해보자:

//create-cat.dto.ts
export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

이제 controller에 메소드의 인수로 body객체를 정의할 수 있다.

@Post()
async create(@Body() createCatDto: CreateCatDto) {
//@Body()데코레이터를 통해 createCatDto를 body객체로 지정하고 CreateCatDto 타입을 지정한다.
  return 'This action adds a new cat';
}

NestJS에서는 별도의 BodyParse가 필요없다.

Full resource sample

Controller의 다양한 기능들을 사용하여 아래와 같은 컨트롤러를 만들 수 있다.

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

컨트롤러를 모두 정의했다면 CatsController를 모듈의 controllers에 넘겨줘야 제대로 라우팅될 수 있다.

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

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

이렇게 AppModuleCatsController를 붙여주면 main.ts에서 AppModule을 통해 app객체를 생성하고 localhost를 통해 서비스된다.

Provider

이렇게 지정된 컨트롤러의 메소드는 보통 Provider를 통해 전달받은 메소드를 작동하는 식으로 구현된다. 컨트롤러 내부에서 작동시킬수도 있지만, 좀더 구조화되고 체계적으로 NestJS를 사용하기 위해서 별도의 Provider를 구현하여 구성해 보자

profile
영원히 공부중

0개의 댓글