Nest 핵심 기술(1) - Controllers

Moen·2024년 9월 25일
0

컨트롤러(Controller)의 목적은 특정 request를 처리하고 클라이언트에 response를 반환하는 역할을 하는 핵심합니다. 컨트롤러(Controller)는 클라이언트로부터 들어온 GET, POST, PUT, DELETE 등의 HTTP requset를 처리하며, 각 요청을 적절한 핸들러 메서드에 매핑하여 response를 반환합니다. 이러한 과정은 라우팅 메커니즘을 통해 이루어집니다.

컨트롤러(Controller)를 생성하기 위해서는 데코레이터(decorator)를 사용합니다. 데코레이터(decorator)는 클래스에 필요한 메타데이터와 연결하고 Nest가 라우팅 맵을 생성할 수 있도록 합니다.

HINT
Nest에서 사용되는 데코레이터(decorator)메타프로그래밍을 지원하는 TypeScript 기능을 기반으로 하며, 클래스, 메서드, 프로퍼티, 매개변수 등에 추가적인 기능**을 정의할 수 있도록 돕는 기능입니다.

Routing

→ [Controller 정의]

컨트롤러(Controller)를 정의하기 위해서는 @Controller 데코레이터를 사용합니다. @Controller는 특정 prefix(경로 접두사)를 설정하여 해당 경로와 관련된 모든 라우터를 처리할 수 있습니다.

예를 들어, 고양이 엔티티와 상호작용하는 모든 라우트를 /cats 경로 아래에 그룹화할 수 있습니다.

// cats.controller.ts

import { Controller, Get } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}

여기서 @Get() 데코레이터는 HTTP GET 요청을 처리하는 핸들러 메서드인 findAll() 을 정의합니다.

→ [라우트 경로]

  • 컨트롤러에 선언된 prefix(경로 접두사)와 HTTP 메서드 데코레이터(@Get, @Post, @Put, @Delete, etc…)에 지정된 경로를 결합하여 라우트가 결정됩니다.
  • 예를 들어, @Controller(’cats’)@Get(’bread’)를 결합하면 GET/cates/bread 라우트가 생성됩니다.
  • prefix(경로 접두사)를 통해 사용자가 원하는 라우트 경로를 지정할 수 있습니다.

→ [표준 응답 처리(recommended)]

  • Nest는 기본적으로 요청 핸들러에서 반환된 데이터를 자동으로 직렬화하여 JSON format으로 응답합니다. Nest에서 자동으로 직렬화하는 반환값은 JavaScript Object 타입이나 Array 타입인 경우 뿐입니다.
  • 기본 JavaScript primitive 타입(string, number, boolean)을 반환할 경우에는 Nest는 직렬화를 시도하지 않고 기본 값을 반환합니다.
  • 응답 상태 코드는 기본적으로 200이며, POST 요청에서는 201을 기본으로 사용합니다. @HttpCode(...) 데코레이터를 통해 상태 코드를 변경할 수 있습니다.

→ [라이브러리별 응답 처리]

  • Nest는 Express나 Fastify 같은 특정 라이브러리의 response object를 직접 사용할 수 있습니다. 이를 위해 @Res() 데코레이터를 사요합니다.
  • 라이브러리별 응답 객체를 사용하여 상태 코드 설정, 헤더 추가 등의 작업을 할 수 있습니다.
@Get()
findAll(@Res() resposne) {
	return response.status(200).send('This action returns all cats')
}
  • @Res()데코레이터를 사용하면 표준 응답 처리 방식이 비활성됩니다. 만약 response object를 사용하면서도 나머지 처리를 Nest에 맡기고 싶다면, @Res({ passthrough: true }) 옵션을 사용할 수 있습니다.

HINT
Nest CLI 명령어
를 사용해서 쉽게 컨트롤러를 생성할 수 있습니다.
$ nest g controller [name] 명령어를 실행하기만 하면 됩니다.

Request Object

Nest는 기본적으로 ExpressFastify 등의 플랫폼에서 requset object(요청 객체)를 처리하며, @Req() 데코레이터를 사용해서 핸들러에서 직접 요청 객체에 접근할 수 있습니다.

// 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';
  }
}

HINT
위 예제처럼 request: Request 타입을 추가하기 위해서 express 타입을 활용할 수 있는 @types/express 패키지를 설치하세요.

@Req() 데코레이터를 사용한 요청 객체(request object)는 HTTP 요청을 나타내며 requset query string, parameters, HTTP headersbody에 대한 프로퍼티를 가지고 있습니다. 대부분의 경우 이런 속성을 수동으로 가져올 필요 없이 @Body(), @Query(), @Param() 등의 데코레이터를 통해 즉시 사용할 수 있습니다. 아래는 Nest에서 제공하는 요청 객체(Request object) 데코레이터입니다.

@Request(), @Req()req
@Response(), @Res()*res
@Next()next
@Session()req.session
@Param(key?: string)req.params / req.params[key]
@Body(key?: string)req.body / req.body[key]
@Query(key?: string)req.query / req.query[key]
@Headers(name?: string)req.headers / req.headers[name]
@Ip()req.ip
@HostParam()req.hosts

응답 객체(response object)를 사용할 때는 @Res() 또는 @Response 데코레이터를 통해 기본 HTTP 플랫폼(Express 및 Fastify) 응답 객체를 직접 관리할 수 있습니다. @Res() 또는 @Response를 사용하면 Nest 라이브러리 전용 모드(Library-specific)로 전환하고 응답(Response)을 관리할 책임이 사용자에게 있다는 점을 유의하세요. 라이브러리 전용 모드를 사용하게 된다면 response 객체(예: res.json(...) 또는 res.send(...))를 호출하여 응답을 보내야 하며, 그렇지 않으면 HTTP 서버가 중단됩니다.

Resources

Nest에서는 HTTP 메서드 데코레이터(@Get(), @Post(), @Put(), @Delete(), @Patch(), @Options(), @Head())를 통해 리소스 엔트포인트를 정의할 수 있습니다. , 또한 @All데코레이터는 모든 메서드를 처리하는 에든포인트를 정의할 수 있습니다.

import { Controller, Get, Post } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
  
  @Post()
  create(): string {
    return 'This action adds a new cat';
  }
  
  @Put()
  modify(): string {
    return 'This action modify a cat';
  }
  
  @Delete()
  delete(): string {
    return 'This action delete a cat';
  }
}

Route wildcares

엔드포인트에 와이드카드 경로를 사용하면 어떠한 문자 조합과도 메서드를 일치시킬 수 있습니다.

@Get('ab*cd')
findAll() {
  return 'This route uses a wildcard';
}

ab*dc 라우트 경로는 abcd, ab_cd, adecd 등과 일치합니다. 문자 ?, +, *, ()는 라우트 경로에 사용할 수 있으며 정규표현식입니다. 하이픈(-),과 점(.)은 문자 그대로 해석됩니다.

HINT
Express에서만 중간 와이드카드가 지원된다는 점을 유의하세요.

Status code

응답 상태 코드는 기본적으로 201인 POST 요청을 제외하고 항상 200입니다. 핸들러 수준에서 @HttpCode(...)데코레이터를 추가하면 쉽게 응답 상태 코드를 변경할 수 있습니다.

@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat';
}

HINT
@nestjs/common 패키지에서 HttpCode를 가져옵니다.

상태 코드는 정적이지 않은 경우 @Res 또는 @Response데코레이터를 사용해서 라이브러리별로 응답 객체를 사용할 수 있으며, 오류가 발생하면 예외를 발생시킬 수 있습니다.

Headers

@Header 데코레이터 또는 @Res(라이브러리별 응답 객체 res.header())를 사용해서 커스텀 응답 헤더를 지정할 수 있습니다.

HINT
@nestjs/common 패키지에서 Header를 가져옵니다.

Redirection

응답을 특정 URL로 리다이렉션하려면 @Redirect(url?: string, statusCode?: number) 또는 @Res() → res.redirect()를 직접 호출해야 합니다.

@Get('default')
@Redirect('https://nestjs.com', 301)

@Get('use-res')
redirectToNewRoute(@Res() res: Response) {
  return res.redirect('/new-route');
}

@Redirect() 데코레이터는 두개의 인수, urlstatusCode를 선택적으로 받을 수 있습니다. statusCode의 기본값은 생략할 수 경우 302(Found)입니다.

반환된 값은 @Redirect()데코레이터에 전달된 모든 인수를 재정의합니다. 예를 들어:

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

Route parameters

Nest에서 Route Parameters(라우트 매개변수)는 URL 경로에서 변수를 받아와 동적으로 처리하는 기능입니다. @Param() 데코레이터를 사용하여 요청 경로의 변수를 캡쳐할 수 있습니다. 예를 들어 id와 같은 매개변수는 URL의 일부로 전달되어 핸들러 함수에서 처리할 수 있습니다.

@Get(':id')
getUserById(@Param('id') id: string) {
  return `User ID is: ${id}`;
}

이 코드는 /cats/123과 같은 요청을 처리하면, id 값이 함수로 전달됩니다.

→ [라우트 매개변수의 주요 개념]

  1. 동적 URL 처리: ****URL 경로에서 변수를 동적으로 받아서 id와 같은 사용자에 따라 동적으로 바뀔 수 있는 입력을 처리합니다.
  2. 매개변수 처리: 하나 이상의 매개 변수를 처리할 수 있습니다.
@Get(':userId/:postId')
getPost(@Param('userId') userId: string, @Param('postId') postId: string) {
  return `User ID: ${userId}, Post ID: ${postId}`;
}

위 코드는 GET cats/123/12와 같은 URL에서 두 개의 매개변수를 동적으로 받아서 처리합니다.

→ [라우트 매개변수와 쿼리 매개변수의 차이]

  • 라우트 매개변수는 URL의 경로 자체에 포함되며, 동적인 경로 데이터를 처리합니다. (@Params 데코레이터 사용)
  • 쿼리 매개변수는 URL 끝에 ?key=value 형식으로 전달됩니다. 검색 필터와 같은 정보를 전달할 때 사용할 수 있습니다.(@Query 데코레이터를 사용합니다.)
// route parameter example
// client request: GET /usrs/123

@Get('users/:id')
getUserById(@Param('id') id: string) {
  return `User ID: ${id}`;
}
// query parameter example
// client requset: GET /users?page-2

@Get('users')
getUserByQuery(@Query('page') page: number) {
  return `Page: ${page}`;
}

→ [동적 라우트 매개변수 사용 시 주의 사항]

동적 라우트 경로를 정적 라우트 경로 뒤에 선언해야 하는 규칙이 있습니다. 이유는 매개변수가 있는 경로는 여러 패턴과 일치할 수 있기 때문에, 정적 경로가 먼저 선언되지 않으면 동적 경로가 먼저 처리되어 정적 경로에 해당하는 요청도 동적 경로로 전달될 수 있기 때문입니다.

@Get('profile')
getProfile() {
  return 'Profile Page';
}

@Get(':username')
getUserByUsername(@Param('username') username: string) {
  return `User: ${username}`;
}

위 예시에서, /profile/john 같은 경로가 있다고 가정하면, 정적 경로(/profile)를 먼저 선언하고 다음에 동적 경로를 선언해야 매개변수화된 경로가 정적 경로의 요청을 가로채지 않습니다.

Sub-Domain Routing

서브 도메인 라우팅은 특정 서브 도메인을 처리해야 할 경우에 사용됩니다. 예를 들어 서브 도메인을 운영하는 서비스에서 각각의 서브 도메인에 대한 다른 로직을 처리해야 할 때 유용합니다.

Nest에서 서브 도메인 라우팅을 사용하기 위해서는 @Controller 데코레이터에 host 옵션 추가하여 특정 HTTP host와 일치시키도록 할 수 있습니다.

@Controller({ host: 'admin.example.com' })
export class AdminController {
  @Get()
  index(): string {
    return 'Admin page';
  }
}

서브 도메인 라우팅에서 동적 값을 처리하는 방법

  1. host option: 경로(path)와 비슷하게 서브 도메인에서도 특정 값을 동적으로 캡쳐할 수 있습니다.
  2. @Controller()에서 host parameter token을 사용하여 서브 도메인의 동적 값을 캡쳐할 수 있습니다.
  3. 이렇게 캡쳐한 동적 값@HostParam() 데코레이터를 통해 메서드에 접근할 수 있습니다.

HINT
host parameter token은 서브 도메인에서 동적 값을 캡쳐하기 위한 방식입니다. 예를 들어, 도메인이 {username}.example.com 일 경우, {username}host parameter token으로 동작하여 사요자 이름을 동적으로 캡쳐할 수 있습니다.

@Controller({ host: ':username.example.com' })
export class UserController {
  @Get()
  getUser(@HostParam('username') username: string) {
    return `Hello, ${username}`;
  }
}

Request payloads

Nest에서 Request Payload는 클라이언트가 서버로 보내는 데이터를 의미하며, 주로 POST, PUT, PATCH 요청에서 사용됩니다. 데이터를 처리하기 위해 @Body() 데코레이터를 사용해서 Request Payload를 처리할 수 있습니다. 타입스크립트로 Nest 프로젝트를 구축하는 경우. DTO(Data Transfer Object)를 사용해서 입력 데이터(request payload)의 구조를 명확하게 정의하고, 데이터의 일관성을 유지하고, 유효성 검사를 쉽게 적용할 수 있습니다.

export class CreateDto {
  @IsString()
  name: string;

  @IsNumber()
  age: number;
}
@Post()
create(@Body() createDto: CreateDto) {
  return this.service.create(createDto);
}

→ [DTO를 클래스로 정의하는 이유]

  1. 클래스런타임 메타데이터를 제공하므로, Nest는 유효성 검사데이터 변환과 같은 기능을 자동으로 처리할 수 있습니다.
  2. 인터페이스컴파일 시점에만 존재하며, 런타임에서는 사용할 수 없으므로, 유효성 검사와 같은 동적 기능을 지원하지 못합니다.
  3. 클래스생성자를 사용하여 데이터의 기본값을 설정하거나 초기화를 할 수 있어서 더 많은 유연성을 제공합니다.

참고 자료

profile
게시글에 잘못된 부분이 있으면 댓글로 알려주시면 빠르게 수정 및 수용도 하겠습니다. 🥲

0개의 댓글