NestJS 공식 문서 Controllers

GGAE99·2023년 6월 27일
0

NestJS 공식 문서

목록 보기
2/33

Controllers

컨트롤러는 요청을 핸들링하고, 클라이언트에게 응답을 반환하는 역할을 합니다.

컨트롤러의 목적은 애플리케이션에 대한 구체적인 요청을 수신하는 것입니다.
라우팅 메커니즘은 어떤 컨트롤러가 어떤 요청을 받는지 제어합니다.
대부분 각 컨트롤러에는 둘 이상의 라우트가 있으며 각각의 라우트는 다른 작업을 수행할 수 있습니다.

기본 컨트롤러를 만들기 위해 클래스와 데코레이터를 사용합니다.
데코레이터들은 클래스를 필요한 메타데이터와 연결시켜주며, Nest가 라우팅 맵을 생성할 수 있도록 도와줍니다. ( 요청을 해당 컨트롤러에 연결 시킵니다. )

빌트인 Validation 과정을 포함하는 간단한 CRUD 컨트롤러를 만드려면, Nest CLI의 CRUD 제너레이터를 사용할 수 있다 : nest g resource [name]

$ nest g resource crud-test
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE src/crud-test/crud-test.controller.ts (971 bytes)
CREATE src/crud-test/crud-test.controller.spec.ts (598 bytes)
CREATE src/crud-test/crud-test.module.ts (270 bytes)
CREATE src/crud-test/crud-test.service.ts (665 bytes)        
CREATE src/crud-test/crud-test.service.spec.ts (475 bytes)
CREATE src/crud-test/dto/create-crud-test.dto.ts (34 bytes)
CREATE src/crud-test/dto/update-crud-test.dto.ts (186 bytes)
CREATE src/crud-test/entities/crud-test.entity.ts (25 bytes)
UPDATE src/app.module.ts (397 bytes)


이렇게 간단하게 crud를 포함한 디렉토리를 만들 수 있다.

Routing

아래 예제에서 기본 컨트롤러를 정의하는 데 필요한 @Controller() 데코레이터를 사용합니다.
@Controller() 데코레이터에게 path prefix를 인자로 넘길 수 있으며, 이를 이용해 연관된 여러 라우트를 한곳에 묶을 수 있게됩니다.

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

@Controller('cats') // path prefix
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
  
  @Post('/create') // cats/create의 경로를 가짐
  createCat(): string {
    return 'createCat'
  }
}

CLI를 이용해 컨트롤러르 생성하려면, nest g controller [name] 커맨드를 사용한다.
위의 경우에는 /cats 라우트로 접근하는 Get 요청은 findAll() 메서드를 실행시키고, return 값을 넘겨준다. /cats/create 라우트로 접근하는 Post 요청은 createCat() 메서드를 실행시킨다.

  • Standard ( 권장됨 )
    이 빌트인 메서드를 사용하면, 요청 핸들러가 JS 객체 또는 배열을 반환할 시, 자동으로 JSON 형식으로 직렬화됩니다. 만약 JS 원시 타입을 반환할 경우, 직렬화시도를 하지 않고 값 그대로를 넘기게 됩니다. 이렇게 Nest가 처리를 해주니 응답 핸들링이 쉬워집니다.
  • Library-specific
    특정 라이브러리에 종속되는 응답 객체를 사용할 수도 있다. 이는 @Res() 데코레이터를 이용해서 주입될 수 있으며, 해당 객체가 노출하는 네이티브 응답 핸들링 메서드를 사용할 수 있게된다. 예를 들어, Express의 경우 다음과 같은 코드로 응답 객체를 만들 수 있다 : response.status(200).send()

Nest는 핸들러의 @Res() 또는 @Next() 데코레이터를 통해서 어떤 라이브러리에 종속되는 응답을 사용하고 있는지 탐지한다. 만약 둘이 동시에 사용되고 있다면, Standard 접근법은 자동으로 비활성화되고, 동작하던대로 동작하지 않게된다. 만약 둘을 동시에 사용하고 싶다면, passthrough 옵션을 true로 설정해줘야 한다 : @Res({ passthrough: true })

Request object

핸들러들은 종종 클라이언트 요청에 대한 정보를 필요로합니다.
Nest는 선택 플랫폼의 요청 객체 에 대한 액세스를 제공합니다. ( 기본은 Express )
@Req() 데코레이터를 통해서 요청 객체를 핸들러에서 사용할 수 있습니다.

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

위 처럼 express 타입이 필요하다면, @types/express 패키지를 설치해야합니다.

npm install @types/express

요청 개체는 HTTP 요청을 나타내며 요청 쿼리 문자열, 매개 변수, HTTP 헤더 및 본문에 대한 속성을 포함합니다. 대부분의 경우 이러한 속성을 수동으로 가져올 필요가 없습니다.
@Body()@Query같이 즉시 사용 가능한 전용 데코레이터를 사용할 수 있습니다.

데코레이터와 이들이 나타내는 일반 플랫폼별 개체 목록
@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

다른 HTTP 플랫폼들과의 타입 호환성을 위해서, Nest는 @Res@Response 데코레이터를 제공합니다.
@Res 데코레이터는 단순히 @Response의 별칭입니다.
둘은 모두 네이티브 플랫폼의 repsonse 객체 인터페이스를 노출하고, 이들을 사용할때는 HTTP 플랫폼에 해당하는 타입을 import 해와야 제대로 사용할 수 있습니다.

Resources

아까는 어떤식으로 주소가 지정되는지 예시를 들기 위해서 Post를 직접 작성했는데, 이번에는 Nest 공식 문서에서 제공해주는 코드를 통해 Cat 리소스를 생성해봅시다.
Cat 리소스를 생성하기 위한 POST 라우트를 정의할 수 있습니다.

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

@Controller('cats')
export class CatsController {
  @Post()
  create(): string {
    return 'This action adds a new cat';
  }

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

Nest는 @Get(), @Post(), @Put(), @Delete(), @Patch(), @Options()@Head()와 같은 모든 표준 HTTP 메서드에 대한 데코레이터를 제공합니다. 또한 @All()은 이들을 모두 처리하는 끝점을 정의합니다.

@All()을 어떻게 사용하는지 예시 필요

Route wildcards

패턴을 이용한 라우트를 정의하는 것도 가능합니다.

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

위의 예시는 abcd, ab_cd,abecd에 해당하는 요청을 처리해줍니다.
nest의 와일드카드 기호인 +, *, ?, ()는 URL 경로의 일부로 사용될 수 있습니다.

@Get('/files/:path+')
getFile(@Param('path') path: string) {
  // 파일 경로를 사용하여 파일 데이터를 반환하는 로직
}
/*
+ 기호는 URL 경로에서 파라미터를 분리하는 데 사용됩니다. 
기본적으로 /로 경로 세그먼트를 구분하지만, +를 사용하면 / 이외의 다른 문자를 파라미터 값에 포함시킬 수 있습니다. 
이는 특히 파라미터 값이 파일 이름 등의 형태를 가지는 경우 유용합니다.
*/
@Get('/users/:id?')
getUser(@Param('id') id?: string) {
  // id 매개변수를 사용하여 사용자 데이터를 반환하는 로직
  // id가 없을 경우 모든 사용자 데이터를 반환할 수도 있음
}
/*
? (선택적 경로 세그먼트):
? 기호는 선택적 경로 세그먼트를 나타내는 데 사용됩니다. 
?를 사용하면 해당 세그먼트가 존재하지 않아도 경로가 일치하게 됩니다. 
*/

() 기호는 패턴 그룹화에 사용됩니다.

StatusCode

디폴트로 반환되는 상태코드는 200이며, POST 핸들러의 경우에만 201입니다.
이는 @HttpCode() 데코레이터를 사용해서 손쉽게 변경할 수 있습니다.

@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat';
}
//@nestjs/common package에서 import 필요

Headers

커스텀 헤더를 지정하기 위해서, @Header() 데코레이터 또는 @Res() 데코레이터를 이용해서 직접 핸들링할 수 있습니다.

@Post()
@Header('Cache-Control', 'none')
create() {
  return 'This action adds a new cat';
}
/*
@Header('Cache-Control', 'none') 데코레이터: 응답의 헤더에 'Cache-Control' 헤더를 추가합니다. 
여기서는 'none'이라는 값을 설정하여 캐싱을 비활성화합니다. 
이렇게 설정된 헤더는 클라이언트에게 응답이 캐시되지 말아야 함을 알려줍니다.
*/
// Import Header from the @nestjs/common package.

Redirection

리다이렉션을 하려면 @Redirect() 데코레이터 또는 res 객체를 사용할 수 있습니다.
@Redirect() 데코레이터는 두가지 인자( url, statusCode )를 받으며, 둘다 옵셔널합니다.
상태 코드의 디폴트 값은 302입니다.

경우에 따라 HTTP 상태 코드 또는 리디렉션 URL을 동적으로 결정해야 할 수 있습니다.
라우트 핸들러 메서드에서 다음과 같은 객체를 반환하면 리다이렉트 하도록 할 수 있습니다.

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

예시

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

Route parameters

요청의 일부분으로 동적인 데이터를 받아야 하는 경우 정적인 경로가 동작하지 않을 수 있습니다.
파라미터와 함께 라우트를 정의하려면, 라우트 파라미터 토큰을 경로 중간에 추가해서 동적인 값을 캐치할 수 있습니다. @Get() 데코레이터 내부에 있는 파라미터 토큰은 @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 { //id 값만 들고오는게 핵심!
  return `This action returns a #${id} cat`;
}

Sub-Domain Routing

@Controller() 데코레이터는 host 옵션을 인자로 받을 수 있는데,
이를 통해서 요청의 HTTP 호스트가 특정 값과 일치하는지 확인하고 요청을 핸들링하도록 할 수 있습니다.

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

Fastify는 지원 안합니다!

라우트 path와 유사하게, hosts 옵션 또한 토큰을 통해서 동적인 값을 체크할 수 있습니다.
이는 @HostParam() 데코레이터를 통해서 핸들러 내에서 받아오고 사용할 수 있습니다.

@Controller({ host: ':account.example.com' })
export class AccountController {
  @Get()
  getInfo(@HostParam('account') account: string) {
    return account;
  }
}

Scopes

다양한 프로그래밍 언어 배경을 가진 사람들에게, 네스트에서는 DB로의 커넥션 풀, 전역 상태를 가진 싱글턴 서비스 등 거의 모든 것들이 요청에 공유된다는 것이 놀라울 수 있습니다. Node js는 요청/응답에 대해 멀티 스레드 모델을 사용하지 않고, 각각의 요청을 각각 다른 스레드가 처리하도록 하기 때문에, 싱글턴 인스턴스를 사용하는 것은 앱 입장에서 안전하다고 볼 수 있습니다.

하지만, 요청 기반의 컨트롤러 라이프타임이 필요한 동작일 때도 있습니다. 예를 들면, GraphQL 애플리케이션에서의 per-request 캐싱이나, 요청 트래킹, multi-tenancy의 경우가 있을 수 있습니다.

Asynchronicity

모던 자바스크립트에서 데이터 추출은 대부분 비동기 작업이기 때문에, Nest는 ansyc 함수와 잘 동작하고, 많은 것을 지원해줍니다.

각각의 비동기 함수는 Promise를 반환해야합니다.
이는 곧 pending 중인 Promise 값을 반환해도 된다는 뜻이기도 합니다.

@Get()
async findAll(): Promise<any[]> {
  return [];
}

위의 코드는 유효하며, Nest 라우트 핸들러들은 RxJS의 Observable 스트림을 반환할 수 있다는 점에서 더욱 강력합니다.

NestJS에서 Observable을 사용하는 이유는 비동기 처리를 지원하기 위해서입니다.
Observable은 비동기적으로 값을 방출할 수 있는 데이터 스트림을 나타내는 개념입니다.
따라서, findAll() 메서드의 반환 타입이 Observable<any[]>으로 선언되어 있습니다.

@Get()
findAll(): Observable<any[]> {
  return of([]);
}

어떤 쪽을 선택해도 괜찮습니다. 요구사항에 맞추어 결정하십셔.

Request payloads

@Body() 데코레이터를 통해서 핸들러에게 요청 객체에 포함된 바디 파라미터를 넘길 수 있습니다.
하지만 그 전에 DTO 스키마를 정의해서 가져올 타입을 정해주는 것이 좋습니다.

타입스크립트 인터페이스 또는 클래스로 DTO를 정의할 수 있는데, 그 중 클래스를 사용하는 것이 권장됩니다. 그 이유는 클래스가 ES6에 포함되어 자바스크립트 표준으로 취급되고, 타입스크립트 코드가 자바스크립트 코드로 트랜스파일링 된 후에도 남아있기 때문입니다. ( 인터페이스는 사라짐 )

// create-cat.dto.ts
export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}
//cat.controller.ts
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  return 'This action adds a new cat';
}

ValidationPipe메서드 핸들러에서 수신해서는 안 되는 속성을 필터링할 수 있습니다!

// cat.controller.ts
  @Post('/update')
  async updateCat(@Body(new ValidationPipe()) updateCatDto: UpdateCatDto): Promise<void> {
    return await this.catService.updateCat(updateCatDto);
  }

Getting up and running

컨트롤러만 정의해놓는다고 해서, Nest가 바로 컨트롤러의 존재여부를 인지하고 인스턴스를 만들 순 없습니다. 컨트롤러는 항상 모듈에 속하며, 이 때문에 @Module() 데코레이터에 controllers 배열이 포함됩니다.

// app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';

@Module({
  controllers: [CatsController],
})
export class AppModule {}

하지만 이 방식은 좋은 방식은 아닙니다.
보통은 CatModule안에 Controller와 Service와 같은 Providers를 넣어주고 app.module.ts에는 CatModule만 넣어줍니다.

0개의 댓글