react, react-native 를 통해 웹/모바일 애플리케이션 개발을 시작하며 백엔드를 설계하고 구현할 기회가 많아졌다.
이전 nodejs express 를 사용해 개발을 하다 nestjs 를 접하게 되었고 많은 부분에서 매력을 느껴 지금은 only nestjs 를 통해서만 백엔드 개발을 하고 있다.
오늘은 nestjs 의 라우팅 메커니즘인 controller class 에 대해 nestjs docs 를 참고하여 정리해 보았다.
Contollers are responsible for handling Incoming request and Returning response to the Client
컨트롤러는 서버가 받은 특정 요청을 수신하는데 목적이 있다. 하나의 라우팅 컨트롤러에는 여러개의 내부 라우팅 경로가 있을 수 있고, 이것은 서로 다른 기능을 수행할 수 있다.
일반적으로도 CRUD 를 수행하는 module 의 컨트롤러라면 최소 4개 정도의 내부 라우팅 경로를 만들었던 기억이 있다.
위의 그림은 https://docs.nestjs.com/controllers 에서 컨트롤러의 역할을 설명하는 도식을 가져온 것이다.
잘 살펴보면, client side 는 사용자가 상호작용하는 모바일/웹 애플리케이션이고 사용자가 서버에게 특정 요청(결제, 로그인 ...)을 하게 되면 미리 정해진 요청의 종류에 따라 경로가 결정되어 가운데 contoroller 로 가게 되는 것을 알 수 있다.
실제로 코드를 작성하고 서버에 요청을 보내봄으로써 nestjs 의 컨트롤러의 동작을 이해해보았다.
import { Controller, Get } from '@nestjs/common';
@Controller('test')
export class TestController {
@Get()
getAll() {
return 'get /test 요청은 싹다 받는다.';
}
@Get('case-a')
getAllCaseA() {
return 'get /test/case-a 요청은 싹다 받는다.';
}
@Get('case-b')
getAllCaseB() {
return 'get /test/case-b 요청은 싹다 받는다.';
}
}
test controller class 는 데코레이터 @Controller('test') 와 함께 선언되어 있는데, 데코레이터에 대한 부분은 나중에 따로 공부를 더 하는게 나을 것 같다.
서버를 실행하고 postman 을 통해 요청을 보내보니 getAll 메소드에서 정의한 리턴값이 반환되는 것을 알 수 있었따.
코드를 보면 경로가 총 3개인데,
1. /test
2. /test/case-a
3. /test/case-b
controller 에 대한 경로에 경로 접두어를 붙여 3가지가 정의된 것을 알 수 있다. 경로를 지정하는 방법은 @Get('경로') 를 통해 경로 접두어를 붙일 수 있다.
이렇게 경로 접두어를 통해 client 로 부터의 요청을 원하는 경로명에 원하는 메소드를 연결 할 수 있는 것이다.
docs 에서 설명하는 request object (이하 req) 는 express api docs 에서 설명하는 HTTP 요청 객체를 의미한다.
req 는 query string, params, body, HTTP header 등등을 포함하고 있다.
컨트롤러를 사용하면서 요청이 원하는 처리를 수행하기 위해서 요청에 포함된 detail 한 요소들에 접근해야 하는 경우가 잦은데, 이럴때 nestjs 의 컨트롤러가 어떻게 처리하는지 코드를 통해 이해해보았다.
import {
Body,
Controller,
Get,
Header,
Param,
Post,
Query,
} from '@nestjs/common';
@Controller('test')
export class TestController {
@Get('profile-query')
getUserProfileQuery(@Query('id') id: string) {
return `user id is ${id}`;
}
@Get('profile-param/:id')
getUserProfileParam(@Param('id') id: string) {
return `user id is ${id}`;
}
@Post('profile-body')
postUserProfileBody(@Body('id') id: string) {
return `user id is ${id}`;
}
}
위의 코드에서 보면 첫번째 경로에서는 req 의 query 를 가져오고, 두번째 경로에서는 req 의 param 을 가져오고, 마지막 경로에서는 req 의 body 를 가져오는 것을 알 수 있다.
이런 작업을 위해 nestjs 에서 기본적으로 제공해주는 데코레이터인 @Query(), @Param(), @Body() 등을 메소드의 매개변수 정의부에서 사용하였다.
사실 express 의 경험을 생각해보면 req.body.id == @Body('id') id 와 같은데 굳이 데코레이터로 접근을 가능하도록 지원하는 것일까?
이 부분은 나중에 nestjs 의 미들웨어를 배우며 조금 해결이 된 것 같다. 내 생각에는, REST API 를 엄격히 구현하기 위해 req 의 query param 과 body 에 신경을 많이 쓰게 되는데,
데코레이터로 req 의 detail 을 명시하고 타입을 검사하고 유효성을 검사하는데 많은 편리함을 주기 때문이 아닐까 생각하게 되었다. 그냥 코드를 봤을때 req 의 특성을 한눈에 들어오게 하는 것도 목적이 아닐까 싶다.
어쨋든, 위와 같이 데코레이터를 통해 req 의 detail 을 접근 할 수 있다!
여기까지가 기초적인 controller 의 개념이었고 추가적으로
1. Promise, Observable 와 controller
2. body payload (with dto..)
3. exception handling
4. header, redirection
등등 이 있는데, 따로 정리를 하는것이 나을 것 같다.