본문으로 바로가기

NestJS 컨트롤러(Controller) [2]

category JavaScript/Nest 2022. 8. 22. 14:00

리디렉션(Redirection)

종종 서버는 요청을 처리한 후 요청을 보낸 클라이언트를 다른 페이지로 이동시키고 싶은 경우가 있습니다.

응답 본문에 redirectUrl을 포함시켜 클라이언트가 스스로 페이지를 이동해도 되지만,

@Redirect 데코레이터를 사용하면 쉽게 구현이 가능합니다.

데코레이터의 두 번째 인자는 상태 코드입니다.

301 Moved permanatly는 요청한 리소스가 헤더에 주어진 리소스로 완전히 이동됐다는 뜻입니다.

이 상태코드를 200과 같이 다른 것으로 바꾸어 응답할 수 있습니다.

하지만 301, 307, 308과 같이 Redirect로 정해진 응답코드가 아닐 경우 브라우저가 제대로 반응하지 않을 수 있습니다.

import { Redirect } from '@nestjs/common';

@Redirect('https://nestjs.com', 301)
@Get(':id')
findOne(@Param('id') id: string) {
  return this.usersService.findOne(+id);
}
$ curl 'http://localhost:3000/users/1' -v
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 3000 (#0)
> GET /users/1 HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< X-Powered-By: Express
< Custom: Test Header
< Location: https://nestjs.com
< Vary: Accept
< Content-Type: text/plain; charset=utf-8
< Content-Length: 52
< Date: Tue, 14 Sep 2021 21:14:59 GMT
< Connection: keep-alive
<
* Connection #0 to host localhost left intact
Moved Permanently. Redirecting to https://nestjs.com* Closing connection 0

만약 요청 처리 결과에 따라 동적으로 리디렉트 하고자 한다면 응답을 다음 객체와 같이 리턴하면 됩니다.

{
  "url": string,
  "statusCode": number
}

예를 들어 쿼리 파라미터로 버전 숫자를 전달받아 해당 버전의 페이지로 이동한다고 하면 다음처럼 구현할 수 있습니다.

@Get('redirect/docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
  if (version && version === '5') {
    return { url: 'https://docs.nestjs.com/v5/' };
  }
}

참고로 앞서 설명했듯이 Nest는 자바스크립트 객체를 리턴하면 JSON 스트링으로 직렬화를 해서 보내줍니다.

이제 브라우저에서 http://localhost:3000/redirect/docs?version=5를 입력하면 https://localhost:3000/redirect/docs/v5/ 페이지로 이동합니다.

 

 

라우트 파라미터

라우트 파라미터는 패스 파라미터라고도 합니다.

라우트 파라미터는 이미 앞선 예제에서 사용했습니다.

1번 유저의 정보를 가져오기 위해  http://localhost:3000/users/1 로 요청합니다.

여기서 1에 해당하는 부분은 유저ID인데 당연히 동적으로 구성됩니다. 즉, 경로를 구성하는 파라미터가 됩니다.

전달받은 파라미터는 함수인자에 @Param 데코레이터로 주입받을 수 있습니다.

 

라우트 파라미터를 전달받는 방법은 2가지가 있습니다.

먼저 파라미터가 여러 개 전달될 경우 객체로 한번에 받는 방법입니다.

이 방법은 params의 타입이 any가 되어 권장하지 않습니다.

물론 라우트 파라미터는 타입이 항상 string이기 때문에 명시적으로 { [key: string]: string } 타입을 지정해 주어도 됩니다.

@Delete(':userId/memo/:memoId')
deleteUserMemo(@Param() params: { [key: string]: string }) {
  return `userId: ${params.userId}, memoId: ${params.memoId}`;
}

일반적인 방법은 다음 코드처럼 라우팅 파라미터를 따로 받는 것입니다.

REST API를 구성할 때 라우팅 파라미터의 개수가 너무 많아지지 않게 설계하는 것이 좋기 때문에 따로 받아도 코드가 많이 길어지지는 않습니다.

@Delete(':userId/memo/:memoId')
deleteUserMemo(
  @Param('userId') userId: string,
  @Param('memoId') memoId: string,
) {
  return `userId: ${userId}, memoId: ${memoId}`;
}

 

 

하위 도메인(Sub-Domain) 라우팅

서버에서 제공하는 기능을 API로 외부에 공개하기로 했다고 가정합시다.

현재 회사가 사용하고 있는 도메인은 example.com이고, API요청은 api.example.com으로 받기로 했습니다.

즉, http://example.com, http://api.example.com로 들어온 요청을 서로 다르게 처리하고 싶습니다.

또한 하위 도메인에서 처리하지 못하는 요청은 원래의 도메인에서 처리되도록 하고 싶습니다.

이런 경우 하위 도메인 라우팅 기법을 쓸 수 있습니다.

 

새로운 컨트롤러를 생성합니다

$ nest g co ApiControler

app.controller.ts에 이미 루트 라우팅 경로를 가진 엔드포인트가 존재합니다.

ApiController에도 같은 엔드포인트를 받을 수 있도록 할 것인데, 이를 위해 ApiController가 먼저 처리될 수 있도록 순서를 수정합니다.

@Module({
  controllers: [ApiController, AppController],
  	...
})
export class AppModule { }

@Controller 데코레이터는 ControllerOptions 객체를 인자로 받는데 host 속성에 하위 도메인을 기술하면 됩니다.

@Controller({ host: 'api.example.com' })	// 하위 도메인 요청 처리 설정
export class ApiController {
  @Get()	// 같은 루트 경로
  index(): string {
    return 'Hello API';	// 다른 응답
  }
}

이제 각각 GET 요청을 보내면 응답을 다르게 주는 것을 볼 수 있습니다.

로컬에서 테스트를 하기 위해 하위 도메인을 api.localhost로 지정하면 curl 명령어가 제대로 동작하지 않습니다.
이는 api.localhost가 로컬 요청을 받을 수 있도록 설정되어 있지 않기 때문입니다.
이를 해결하기 위해 /etc/hosts 파일의 마지막에 127.0.0.1 api.localhost 을 추가하고 서버를 다시 구동하면 됩니다.
윈도우의 경우도 해결방법이 있으니 다른 자료를 참고바랍니다. (맥북/리눅스의 경우는 '더보기'를 참고하세요.)
더보기

아이피 도메인처럼 설정

cd /
cd ./private
cd ./etc
sudo vi hosts

127.0.0.1 localhost.domain.com    // 다음과 같이 추가(예시)

 ESC 클릭 후 :wq 또는 :!wq 로 저장 후 나가기

ls -al 명령어를 사용하여 해당 폴더에 파일 또는 폴더가 존재하는지 확인하면서 진행.

cd로 해당 디렉토리로 접근할 땐 cd만 치고 Tab을 눌러 파일 확인 및 자동완성(일부 입력시)도 가능

 

위 처럼 설정하면 localhost:3000도 접속이 가능하지만

localhost.domain.com:3000 으로도 동일하게 접속 가능해짐.

또한 없는 도메인 혹은 http 접속으로 하면 아무런 주소로도 가능한것 같았다.

http://localhost

http://localhost.domain.com

 

http://19-97.domain.com

 

http://naver.com

 

@Controller({ host: 'api.localhost' })	// localhost로 변경
export class ApiController {
  @Get()
  index(): string {
    return 'Hello, API';
  }
}

이제 각각 요청을 보내면 원하는대로 잘 동작합니다.

$ curl http://localhost:3000
Hello World!
$ curl http://api.localhost:3000
Hello, API

앞서 우리는 요청 패스를 @Param 데코레이터로 받아 동적으로 처리할 수 있었습니다.

유사하게 @HostParam 데코레이터를 이용하면 서브 도메인을 변수로 받을 수 있습니다.

API 버저닝을 하는 방법이 여러가지 있지만 하위 도메인을 이용하는 방법을 많이 사용합니다.

다음과 같이 하위 도메인 라우팅으로 쉽게 API를 버전별로 분리할 수 있습니다.

@Controller({ host: ':version.api.localhost' })
export class ApiController {
  @Get()
  index(@HostParam('version') version: string): string {
    return `Hello, API ${version}`;
  }
}
$ curl http://v1.api.localhost:3000
Hello, API v1
$ curl http://api.localhost:3000
Hello World!

host params이 없는 host로 요청을 하면 기존 도메인으로 요청이 처리되는 것을 볼 수 있습니다.

 

 

페이로드(Payload) 다루기

POST, PUT, PATCH 요청은 보통 처리에 필요한 데이터를 함께 실어 보냅니다.

이 데이터 덩어리(페이로드)를 본문(body)이라고 합니다.

NestJS는 본문을 DTO(Data Transfer Object)를 정의하여 쉽게 다룰 수 있습니다.

 

앞서 생성한 Users 리소스를 생성하기 위해 POST /users 로 들어오는 본문을 CreateUserDto로 받고 있습니다.

회원가입을 하기 위해 이름과 이메일을 추가해 봅시다.

export class CreateUserDto {
  name: string;
  email: string;
}
@Post()
create(@Body() createUserDto: CreateUserDto) {
  const { name, email } = createUserDto;
  
  return `유저를 생성했습니다. 이름: ${name}, 이메일: ${email}`;
}

이제 유저 생성 API를 요청하고 본문에 데이터가 잘 들어가 있는지 확인해 봅시다.

$ curl -X POST http://localhost:3000/users -H "Content-Type: application/json" -d 
'{"name": "Dexter", "email": "dexter@example.com"}'
유저를 생성했습니다. 이름: Dexter, 이메일: dexter@example.com

GET 요청에서 서버에게 전달할 데이터를 포함할 때는 일반적으로 요청 주소에 포함시킵니다.

예를 들어 유저 목록을 가져오는 요청은 GET /users?offset=0&limit=10 과 같이 페이징 옵션이 포함되도록 구성할 수 있습니다.

offset은 데이터 목록 중 건너뛸 개수를 의미하고, limit은 offset 이후 몇 개 의 데이터를 가져올 지 결정합니다.

이 두 개의 쿼리 파라미터를 @Query DTO로 묶어 처리할 수 있습니다.

export class GetUserDto {
  offset: number;
  limit: number;
}

 

 

 

참고: NestJS로 배우는 백엔드 프로그래밍

 

반응형