controller는 client로 부터 요청과 그에 대한 응답을 관리하는 역할을 갖고 있습니다.
컨트롤러의 라우팅 메커니즘은 각각의 요청마다 알맞은 controller에 전달 후 controller에서 알맞은 라우팅을 통해 요청이 전달되고 응답을 제공합니다.
각 controller는 하나 이상의 라우팅을 갖고 있고, 각각의 라우팅은 다른 기능을 제공합니다.
팁 nest CLI의 nest g resource [name]를 통해 빠르게 CRUD를 위한 구조를 생성할 수 있습니다.
라우팅
cats.controller.ts
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
위의 내용은 nest 공식문서에 있는 cat 컨트롤러 입니다.
@Controller()
@Controller()는 기본 컨트롤러를 정의하는 방법입니다. 이를 통해 우리는 라우팅 경로의 접두사를 지정할 수 있습니다. 위의 예시에서는 요청의 경로는 /cats를 통해 전달됩니다. '@'가 붙은 친구들을 데코레이터라 칭합니다.
@Get()
@Get() 데코레이터는 HTTP 메소드와 연결됩니다. 위의 예시에서는 GET /cats의 HTTP 요청을 처리할 수 있습니다. @Get()과 유사하게 @Post(), @Put(), @Delete()등을 사용할 수 있으며, 해당 메소드에 따라 /cats뒤의 경로를 @Put('custom')과 같이 정의할 수 있습니다.
위의 예제에서 GET /cats요청이 들어오면 우리가 정의한 findAll()함수를 호출합니다. 이때 만드는 함수의 이름은 자유롭게 지어도 상관없습니다.
response
위의 예제에서 상태코드 200과 함께 "This action returns all cats"를 응답합니다. 여기서 응답은 자동으로 요청의 메소드에 맞게 생성이 되는데 우리의 입맞에 맞게 변경하고 싶다면 아래의 두가지 방법을 참고하면 되겠습니다.
1. 표준의 방법(추천되는 방법)
표준의 방법을 사용하면 응답이 JSON 직렬화 되어야 하는 경우(object 자료형을 응답하는 경우)에는 알아서 직렬화를 해주며, 문자열, 숫자와 같은 응답을 하는경우 직렬화를 하지 않고 nest가 알아서 응답의 구조를 만들어서 응답해줍니다. 만약 상태코드를 변경하고 싶다면 아래와 같이 수정하면 되겠습니다. 자세한 HttpCode
import { HttpCode } from '@nestjs/common'; // HttpCode를 import 해야합니다.
@Post()
@HttpCode(204)
create() {
return 'This action adds a new cat';
}
2. express처럼 응답하기 (기능별 옵션)
@Res()데코레이터를 다음와 같이 사용하면findAll(@Res() response) response를 사용하여 응답값을 response.status(200).send('응답입니다.')와 같이 지정할 수 있습니다.
🚨주의 nest는 @Res(), @Next()의 사용을 감지하여 기능별로 응답을 하는것으로 인지합니다. 이때 @Res(), @Next()두가지를 동시에 사용하게 되면 기능별로 응답하는것이 자동적으로 제한이 되며 우리가 예측한 응답을 얻을 수 없습니다. 이렇게 두가지를 동시에 사용하고 싶다면 @Res({ passthrough: true })과 같은 옵션을 통해 사용해야 합니다. 잘 생각해보면 우리가 express에서도 res를 사용하게 되면 next를 통해 다음 미들웨어로 이어나가지 않고 바로 응답이 되었습니다. 이러한 논리와 바슷하게 @Res(), @Next()의 동시사용이 불가능하다고 생각합니다.
Request object 받아오기
@Req()
@Req()데코레이터를 통해서 request 객체를 전달 받을 수 있다. 사용법은 다음과 같습니다.
cats.controller.ts
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';
}
}
dto(Data Transfer Object) 입력받을 데이터의 구조를 미리 선언하는 것입니다. 입력받을 데이터의 구조와 맞지 않는다면 입력 차제를 받아들이지 않기 때문에 엄격하다고 생각할 수 있습니다. 하지만 이러한 엄격성이 typescript와 맞다고 생각하고, 유연한 구조와 비교해서 오류를 미연에 방지하는 기능이 우월하다고 생각합니다.
위는 catcontroller에 고양이 생성을 위해 필요한 입력을 명시한 dto입니다. 아래와 같이 활용할 수 있습니다.
cats.controller.tsJS
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
ValidationPipe
위에서 명시한 dto의 규격과 다른 입력이 들어왔을 경우 우리는 ValidationPipe를 통해 에러를 응답할 수 있습니다. 사용은 아래와 같이 사용하면 됩니다.
app.useGlobalPipes(
new ValidationPipe({
disableErrorMessages: true,
whitelist: true,
}),
);
disableErrorMessages: true 오류를 응답하는 메세지에 자세한 원인을 나타내지 않는것이 보안상의 이유로 좋습니다. 물론 client에게 왜 오류가 발생했는지 자세한 메세지를 받아서 응답하면 좋겠지만, 우리의 서버의 오류에 대해 자세한 정보를 여러 사용자들에게 노출하게 되면 보안상 문제가 생길 수 있습니다.
whitelist: true dto에서 명시한 내용을 제외한 다른 정보를 제외하고 request에 담는 기능을 제공합니다. 예를들면 우리가 로그인을 위해 ID, password만 필요한 상황에서 age값이 request객체에 포함되어 있을 경우 age값을 제외하고 ID, password만 변수에 담아서 제공하는 기능을 합니다.
forbidNonWhitelisted 위의 whitelist: true 상황처럼 dto와 다른 요청의 내용이 request 객체에 담겨있다면 해당하는 요청 자체를 진행하지 않고 error를 응답하는 기능을 제공합니다.