NestJS 파헤치기2 - Controller

재로미·2022년 10월 15일
0

nestjs

목록 보기
2/5

Overview: App Architecture

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에 대해 공부해보겠다.

Controllers

이전 포스트에서 그림을 통해 유저의 HTTP request와 response가 서버를 통해 들어오고 나간다고 하였다.
controller 이미지

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')와 같이 표현할 수 있다.

Request Object

핸들러는 종종 클라이언트 요청 세부정보에 엑세스할 필요가 있다. 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';
	}
}

nest에서 제공하는 controller 관련 데코레이터

그리고, 실제 개발에서 많이 사용하는 것이 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`;
}

Request payloads

HTTP 호출 중에 Post를 사용하면 request에 body를 실어보내게 된다. 이 payload를 nest에서는 @Body() 데코레이터로 받을 수 있고 이 body로 전달되는 payload를 담는 객체를 따로 생성해서 관리하면 편하다.

이 때 사용하는 객체는 DTO(Data Transfer Obejct)라고 하여 아래와 같은 특징과 장점을 갖는다.

DTO(Data Transfer Object)

  • DTO는 소프트웨어 개발에서는 기본적인 개념
  • class나 interface로 정의할 수 있는데 class를 쓰는 것이 좋음
    • 이는 class는 JS ES6 표준의 일부라서 TS →JS로 컴파일되어도 실제 엔티티로 유지되나 interface는 트랜스파일 중에 제거되기 때문에 NestJS는 런타임시 이를 참조할 수가 없기 때문
  • DTO는 모델을 정의하는 것이 아니라, 어떤 특정한 케이스, 작업에서 요구하는 데이터의 형태를 정의하는 것임
  • 어떤 고유의 데이터의 일련화, 비일련화, 검색, 스토리지 등을 제외하면 어떤 동작도 갖지 않음
  • 데이터 유효성 검사에서도 유용하게 사용될 수 있음

이런 특징을 가지고 애플리케이션 개발에 있어 편리함과 직관성, 간결함을 주고 나아가 코드를 유지보수, 확장하는데에 도움이 된다.

이 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:

profile
정확하고 체계적인 지식을 가진 개발자 뿐만 아니라, 가진 지식을 사람들과 함께 나눌 수 있는 계발자가 되고 싶습니다

0개의 댓글