컨트롤러의 책임은 클라이언트의 요청을 처리하고 응답을 반환하는 것입니다.
라우팅 메커니즘은 앱에 대한 요청을 어떤 컨트롤러가 처리해야하는지 결정합니다.
보통 하나의 컨트롤러는 하나 이상의 라우트를 가지고 있으며 각 라우트들은 서로 다른 액션을 취할 수 있습니다.
컨트롤러를 만들기 위해서는 클래스와 데코레이터를 사용합니다. 데코레이터는 클래스와 필요한 메타 데이터들을 연결하고, 요청을 컨트롤러와 연결하는 routing map을 생성할 수 있도록 합니다.
그러면 users controller를 만들어 봅시다.
import { Controller, Get } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
findAll(): string {
return 'This actions returns all cats';
}
}
위와 같이 클래스 위에 @Controller()
데코레이터를 호출하면 해당 클래스는 데코레이터의 인수로 넘겨진 문자열을 api 호출 base 경로로 처리하는 컨트롤러가 됩니다.
그리고 클래스 내의 메서드에는 HTTP 메서드에 해당하는 데코레이터를 호출하여 해당 엔드포인트 요청에 대한 핸들러로 만들어 줄 수 있습니다.
메서드에 붙은 HTTP 메서드에 해당하는 데코레이터에도 url 경로를 인수로 넘겨줄 수 있으며, 이렇게 해서 Controller에 지정한 base 라우트 아래의 User 엔티티와 관련된 라우트들을 그룹화 할 수 있습니다.
현재는 @Get()
데코레이터에 아무것도 넘겨주지 않았습니다. 따라서 findAll()
메서드는 GET /users 를 처리하는 api가 됩니다.
import { Controller, Get, HttpCode } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
@HttpCode(200)
findAll(): string {
return 'This actions returns all cats';
}
}
@HttpCode
데코레이터를 통해 상태코드 추가도 가능합니다.
Nest는 기본적으로 객체나 배열을 반환하는 경우 자동으로 JSON으로 직렬화 해주지만, number, string, boolean
등의 원시 타입 값을 반환하는 경우에는 JSON으로 직렬화하지 않습니다.
아니면 @Res()
데코레이터를 사용해서 특정 응답 객체를 주입받을 수 있습니다.
import { Controller, Get, HttpCode, Res } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
@HttpCode(200)
findAll(@Res() response): string {
return response.status(200).send('This actions returns all users');
}
}
이렇게 하면 응답 객체에 노출되어있는(exposed) 기본적인 응답 처리 메서드를 사용할 수 있습니다.
위는 Express를 사용한 예입니다.
핸들러에서 보통 요청에 대한 상세 정보가 필요한 경우가 많습니다.
Nest에서는 기본 플랫폼(Express가 기본값)의 요청 객체에 대한 접근할 수 있습니다.
핸들러 시그니처에 @Req()
데코레이터를 추가해서 Nest가 요청 객체(request object)를 주입하도록 만들 수 있습니다.
import { Controller, Get, HttpCode, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller('users')
export class UsersController {
@Get()
@HttpCode(200)
findAll(@Req() request: Request): string {
return 'This actions returns all users';
}
}
HTTP 요청을 나타내는 request 객체는 쿼리 스트링, 파라미터, HTTP 헤더, 바디 등의 속성을 가지고 있습니다.
Express에서 주로 사용하던 것들입니다. 주로 req.query
이런식으로 사용해왔는데 Nest에서는 이러한 속성을 바로 가져오는 데코레이터들을 사용할 수 있습니다.
@types/express
를 설치해서 타이핑의 이점을 누릴 수 있도록 하는 것이 좋습니다.
Nest에서는 간단하게 모든 기본 HTTP method 종류를 데코레이터로 제공합니다.
@Post(), @Put(), @Delete(), @Patch(), @Options(), and @Head()
.
@All()
은 모든 종류의 HTTP 메서드를 처리하는 핸들러로 정의할 수 있습니다.
route에 Express에서 사용하는 문자열 패턴 매칭과 와일드 카드도 사용 가능합니다.
@Get('ab*cd')
findAll() {
return 'This route uses a wildcard';
}
커스텀 응답 헤더를 명시하기 위한 @Header()
를 사용할 수 있습니다.
@Post()
@Header('Cache-Control', 'none')
create() {
return 'This action adds a new cat';
}
리다이렉트를 위해서 Nest에서는 @Redirect()
데코레이터를 제공합니다.
@Redirect()
데코레이터는 두 url: string
, statusCode: number
를 매개변수로 갖습니다.
이 둘은 선택값이고 생략되면 302가 기본입니다.
@Get()
@Redirect('https://nestjs.com', 301)
리다이렉트 url과 statuc code를 동적으로 결정하고 싶다면 아래와 같은 형태의 객체를 핸들러 메서드의 응답으로 사용하면 됩니다.
{
"url": string,
"statusCode": number
}
이렇게 응답 객체를 반환하면 @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에서 특정 위치의 동적 값을 캡처하기 위해 라우트 경로에 라우트 매개변수 토큰을 추가할 수 있습니다.
선언된 라우트 파라미터는 핸들러 메서드의 시그니처에 @Param()
데코레이터를 추가해서 접근할 수 있습니다.
@Get(':id')
findOne(@Param() params: any): string {
console.log(params.id);
return `This action returns a #${params.id} cat`;
}
@Param()
데코레이터를 메서드 파라미터에 추가하면, 위와 같이 라우트 파라미터들을 데코레이팅된 메서드 파라미터의 속성으로 사용할 수 있습니다.
아니면 아래와 같이 특정 라우트 파라미터 토큰을 데코레이터의 인수로 전달하여 라우트 파라미터 이름으로 값에 직접 접근할 수 있습니다.
@Get(':id')
findOne(@Param('id') id: string): string {
return `This action returns a #${id} cat`;
}
@Controller
데코레이터는 호스트 옵션을 사용하여 들어오는 요청의 HTTP 호스트가 명시된 값과 일치하는지 확인할 수 있습니다.
@Controller({ host: 'admin.example.com' })
export class AdminController {
@Get()
index(): string {
return 'Admin page';
}
}
host
옵션으로 host name의 특정 위치에 동적 값을 캡쳐할 수 있습니다.
@Controller({ host: ':account.example.com' })
export class AccountController {
@Get()
getInfo(@HostParam('account') account: string) {
return account;
}
}
@Controller
데코레이터의 host 옵션에 지정된 host parameter 토큰입니다. 이렇게 선언된 host parameter는 메서드 시그니처에 @HostParam()
데코레이터를 추가함으로써 접근할 수 있습니다.
Node.js는 request/response Multi-Threaded Stateless Model(모든 요청이 각각 별도의 스레드에 의해 처리되는 모델)을 따르지 않습니다. 따라서 Node에서 싱글톤 인스턴스를 쓰는 것은 안전합니다.
@Get()
async findAll(): Promise<any[]> {
return [];
}
async 함수는 반드시 Promise
를 반환합니다. Nest 라우트 핸들러에서 값을 반환하면 스스로 resolve 된 값을 반환할 수 있습니다.
또한 라우트 핸들러는 RxJS 옵저버블 스트림을 반환할 수 있습니다. Nest는 자동으로 아래의 소스를 구독하고 스트림이 완료되면 마지막으로 방출된 값을 가져옵니다.
@Get()
findAll(): Observable<any[]> {
return of([]);
}
@Body()
데코레이터를 추가하여 클라이언트의 요청 페이로드에 접근할 수 있습니다.
TypeScript를 사용하고 있다면 DTO 스키마를 먼저 작성해야 합니다.
TS 인터페이스나 클래스를 사용해서 DTO 스키마를 작성할 수 있습니다. 클래스를 사용하는 것을 추천하는데 클래스는 ES6 표준이기 때문에 TS를 컴파일한 JS에도 그대로 클래스가 보존되기 때문입니다.
인터페이스를 사용하면 컴파일된 JS 파일에서는 사라지기 때문에 runtime에 Nest가 참조할 수 없게 됩니다.
Pipe같은 기능으로 runtime 때 변수의 메타타입에 접근할 가능성이 있기 때문에 클래스로 DTO를 정의하는 것이 좋습니다.
export class CreateCatDto {
name: string;
age: number;
breed: string;
}
이렇게 DTO 클래스를 정의하고
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
DTO를 라우트 핸들러 시그니처에 매개변수의 타입으로 지정하여 요청 페이로드를 DTO 타입으로 전달받을 수 있습니다.