NestJS 앱을 생성하면 기본적으로 app.controller.ts
, app.module.ts
, app.service.ts
등으로 보일러플레이트가 구성되어 있고 이를 기본으로 애플리케이션이 구동된다.
지난 포스트 말미에서 다룬 것처럼 Nest App을 최초 생성했을 때 아래와 같이 파일이 생성된다.
src
|- main.ts # Nest 앱의 실행파일
|- app.module.ts # 실행파일에서 등록, 사용하는 root 모듈파일
|- app.controller.ts # Nest 앱의 root 기본 컨트롤러
|- app.service.ts # Nest 앱의 root 기본 비즈니스 서비스
기본적인 역할들은 주석에 명시해놓았지만 그래도 처음 보면 이해하기 어렵다. 하나씩 개념과 그 의의를 살펴보자. 이번 포스트에서는 Controllers에 대해 공부해보겠다.
이전 포스트에서 그림을 통해 유저의 HTTP request와 response가 서버를 통해 들어오고 나간다고 하였다.
Controller의 목적은 클라이언트 사이드로 들어오는 요청을 routing 매커니즘을 거쳐 받은 다음 business logic에 해당하는 provider로 역할을 위임하여 결과를 response로 반환하는 것이다. 쉽게 말하자면 우리가 어떤 매장에 들어갔을 때 인원을 체크하고 빈 자리로 안내해주는 매장 직원 역할을 한다고 생각하면 되겠다.
NestJS는 Controller는 REST Api와 GraphQL을 지원하며, HTTP Request에 따라 GET, POST 등에 대한 비즈니스 로직을 적용시킬지 설정만 담당하면 된다.
// app.controller.ts
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();
}
}
nest 앱을 만들 때 생성되는 app.controller.ts
의 내용이다. @Controller 라는 데코레이터를 사용하여 컨트롤러임을 명시할 수 있고, @Controller('cats') 처럼 경로 접두사를 넣어 관련 경로 세트로 그룹화 할 수 있다.
Nest CLI를 사용해서 app 하위 컨트롤러를 간단히 생성할 수도 있다.
$ nest g controller cats
# cats 폴더와 cats.controller.ts , cats.controller.spec.ts 파일이 생성된다.
// cats.controllers.ts
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
/*
Nest는 GET/cats 요청을 이 핸들러에 매핑함.
위 예제에서 이 endpointdp GET 요청이 있을 때 Nest는 요청을 커스텀 findAll() 메서드로 라우팅함.
이 메서드는 200 상태 코드와 관련 응답을 반환함
*/
@ 데코레이터를 사용해서 controller 클래스 내 메서드를 @Get @Post @Put @Patch 등으로 감쌀 수 있고, 심화하여 커스텀 데코레이터를 사용할 수도 있다.
이런 REST API 메서드에서도 경로를 넣을 수 있는데, @Controller('cats') 내 findAll 메서드에 @Get('all')을 달았다면 http:{{host}}/cats/all 으로 GET을 날린다는 의미가 된다. 이 경로는 와일드카드를 사용한 패턴 기반 경로도 지원하며 @Get('ab*cd')와 같이 표현할 수 있다.
핸들러는 종종 클라이언트 요청 세부정보에 엑세스할 필요가 있다. Nest는 기본 플랫폼(Express)의 요청 객체에 대한 액세스를 제공하는 데 이는 @Req() 데코레이터를 추가해서 Nest에 주입하도록 지시해서 요청객체(HTTP 요청, 이는 요청 쿼리 문자열, 매개변수, HTTP 헤더 및 본문 속성을 포함)에 액세스할 수 있다는 것이다.
// cats.controllers.ts
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express'; // express 타입 활용을 위해서는 @types/express 설치 필요
@Controller('cats')
export class CatsController {
@Get()
findAll(@Req() request: Request): string {
// 물론 @Req로 다 가져오지 않고 @Body(), @Query() 전용 데코레이터로 특정 속성을 가져올 수 있음
return 'This action returns all cats';
}
}
그리고, 실제 개발에서 많이 사용하는 것이 route parameter인데 이는 특정 path로 정적인 것이 아닌 id처럼 사용자, 상황별로 다른, 입력 문자가 일정하지 않은 동적 데이터를 처리해야 할 때 @Get(':id')
처럼 URL에서 id에 해당하는 동적값을 캡처하여 여기에 사용되는 매개 변수는 @Param() 데코레이터로 엑세스할 수 있다.
이 외에도 @Req(), @Res(), @HttpCode() 등도 지원한다.
@Get(':id')
findOne(@Param() params): string {
console.log(params.id);
return `This action returns a #${params.id} cat`;
}
// 매개변수는 @Param 데코레이터로 받아서 처리. params.id로 라우팅 매개변수 값을 추출한다면,
@Get(':id')
findOne(@Param('id') id: string): string {
return `This action returns a #${id} cat`;
}
HTTP 호출 중에 Post를 사용하면 request에 body를 실어보내게 된다. 이 payload를 nest에서는 @Body() 데코레이터로 받을 수 있고 이 body로 전달되는 payload를 담는 객체를 따로 생성해서 관리하면 편하다.
이 때 사용하는 객체는 DTO(Data Transfer Obejct)라고 하여 아래와 같은 특징과 장점을 갖는다.
이런 특징을 가지고 애플리케이션 개발에 있어 편리함과 직관성, 간결함을 주고 나아가 코드를 유지보수, 확장하는데에 도움이 된다.
이 DTO를 사용하여 Cat을 생성하는 controller method를 다음과 같이 만들 수 있다.
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
/* POST http://localhost:3000/cats
BODY PAYLOAD
{
name: 'testCat', -> string
age: 11, -> number
gender: 'male', -> string
partnerName: 'Jerome' -> string
}
*/
이런식으로 완성한 컨트롤러는 nest App 실행시 기본적으로 체크를 하지못하는데 이는 이 클래스의 인스턴스를 만들지 않아서이다.
따라서 다음 포스트에 상세히 다루겠지만 Module의 controller로 따로 등록을 해야하며, 이 Cat module을 App.module로 등록했을 때 nest App에서는 해당 route로 들어오는 request와 response를 우리가 정의한 controller에 맞게 핸들링 하게된다.
Reference: