NestJS Controllers

paduck·2024년 6월 10일
0

NestJS

목록 보기
3/24

Controllers는 들어오는 요청을 처리하고 클라이언트에 응답을 반환하는 역할

  • 라우팅 메커니즘
    어떤 controller 가 어떤 요청을 받는지 제어
    • 단일 controller 에 여러 경로가 있더라도 각자 다른 작업 수행 가능
    • 클래스와 데코레이터를 통해 만들 수 있음

데코레이터:
클래스에 필요한 메타데이터를 연결하고, 요청을 controller 에 연결 해 Nest 가 라우팅 맵을 생성할 수 있게 함

ValidationPipe 내장된 CRUD Controller를 생성하려면 CLI의 CRUD generator를 사용: nest g resource [name]

Routing

기본 Controller를 정의하기 위해 반드시 필요한 @Controller() 데코레이터

  • @Controller() 데코레이터에 경로 접두사를 사용하면 관련된 경로 집합을 쉽게 그룹화하고 반복적인 코드 생략 가능
import { Controller, Get } from '@nestjs/common';

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

CLI를 사용한 Controller 생성은 $ nest g controller [name] 명령어로 가능

@Get() HTTP 요청 메서드 데코레이터는 findAll() 메서드가 특정 HTTP 요청을 처리

  • cats라는 경로 접두사를 사용하여 GET /cats 요청을 이 메서드로 매핑

200 상태 코드와 함께 문자열을 반환

  • Nest 응답을 처리하는 두 가지 다른 옵션을 제공합니다:
표준 (권장) 요청 핸들러가 JavaScript 객체나 배열을 반환하면 자동으로 JSON으로 직렬화됩니다. JavaScript 원시 타입(예: string, number, boolean)을 반환하면 해당 값을 직렬화하지 않고 그대로 전송합니다. 기본 상태 코드는 POST 제외(201), 200입니다. @HttpCode(...) 로 변경 가능합니다.
라이브러리 특정 라이브러리 특정 (예: Express) 응답 객체를 사용할 수 있습니다. 이 객체는 메서드 핸들러 시그니처에 @Res() 데코레이터를 사용하여 주입할 수 있습니다 (예: findAll(@Res() response)). 이렇게 하면 원래 응답 처리 메서드를 사용할 수 있습니다. (예: Express 상에서는 response.status(200).send() )

Warning 핸들러가 @Res() 또는 @Next()를 사용할 때 Nest는 라이브러리 특정 옵션을 선택한 것으로 감지합니다. 두 가지 접근 방식을 동시에 사용하면 표준 접근 방식이 비활성화됩니다. 둘 다 사용하려면 @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 패키지를 설치

요청 객체는 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

Resources

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는 모든 표준 HTTP 메서드를 위한 데코레이터를 제공

  • @Get(), @Post(), @Put(), @Delete(), @Patch(), @Options(), @Head(), @All()-모든 메서드 처리

Route wildcards

라우팅 경로에 패턴 적용 가능

  • hyphen(-) 과 dot(.) 은 문자열로 인식

Warning 경로 중간에 와일드카드를 사용하는 것은 Express에서만 지원

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

Status code

응답 상태 코드는 기본적으로 200이나, POST 요청의 경우 201

  • @HttpCode(...) 데코레이터로 변경 가능
@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat';
}

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

고정되어 있지 않은 상태 코드는 특정 응답 객체 (@Res()를 사용한 주입) 혹은 예외를 던져서 처리

Headers

맞춤 응답 헤더를 지정하려면 @Header() 데코레이터나 특정 응답 객체 (res.header())를 직접 사용

@Post()
@Header('Cache-Control', 'none')
create() {
  return 'This action adds a new cat';
}

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

Redirection

응답을 리디렉션하려면 @Redirect() 데코레이터나 특정 응답 객체 (res.redirect())를 사용

@Redirect()urlstatusCode 를 인수로 받으나, 둘 다 선택 사항이며, 기본 statusCode 값은 302

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

동적인 HTTP 상태 코드나 리디렉션 URL은 @nestjs/commonHttpRedirectResponse 인터페이스를 따르는 객체를 반환하여 이를 수행할 수 있습니다.

반환된 값은 @Redirect() 데코레이터의 값을 override

@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

정적 경로로는 요청에 있는 동적 데이터를 받아들일 수 없음
(예: GET /cats/1에서 ID가 1인 고양이를 가져옴)

  • 경로 매개변수 토큰을 추가해 경로에 동적 값 확인 가능
    • 메서드 시그니처에 추가된 @Param() 데코레이터 통해 접근 가능
@Get(':id')
findOne(@Param() params: any): string {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}

매개변수가 있는 경로는 정적 경로 뒤에 선언되어야 합니다. 이렇게 하면 매개변수화된 경로가 정적 경로로 향하는 트래픽을 가로채는 것을 방지할 수 있습니다.

  • @Param()은 메서드 매개변수(params)를 데코레이트하여, 메서드 본문 내에서 경로 매개변수에 접근
  • 특정 매개변수 토큰을 데코레이터에 전달하여 경로 매개변수를 이름으로 직접 참조
@Get(':id')
findOne(@Param('id') id: string): string {
  return `This action returns a #${id} cat`;
}

Param@nestjs/common 패키지에서 가져옵니다.

Sub-Domain Routing

@Controller 데코레이터는 host 옵션을 사용하여 들어오는 요청의 HTTP 호스트가 특정 값과 일치하는지 요구할 수 있음

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

Warning Fastify는 중첩된 라우터를 지원하지 않으므로, 서브 도메인 라우팅을 사용할 때 (기본) Express 어댑터를 사용해야 합니다.

  • hosts 옵션도 토큰을 통해 호스트 이름의 해당 위치에서 동적 값을 캡처
@Controller({ host: ':account.example.com' })
export class AccountController {
  @Get()
  getInfo(@HostParam('account') account: string) {
    return account;
  }
}

Scopes

데이터베이스에 대한 연결 풀, 전역 상태를 가진 싱글톤 서비스 등 Nest에서 거의 모든 것이 들어오는 요청 간에 공유
그러나, GraphQL 애플리케이션에서 요청별 캐싱, 요청 추적 또는 멀티 테넌시 등 그러나 요청 기반의 컨트롤러 수명이 필요한 경우

  • 범위를 제어하는 방법은 여기에서 확인하세요.

Asynchronicity

대체로 비동기인 데이터 요청에 흐름에 따라 Nest는 async 함수를 지원

async / await 기능에 대해 자세히 알아보려면 여기를 참조하세요.

모든 async 함수는 Promise를 반환

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

또한, Nest 경로 핸들러는 RxJS observable streams을 반환할 수 있음

  • 자동적으로 기반 소스를 subscribe 하고 마지막 값(stream 이 완료되면)을 활용
@Get()
findAll(): Observable<any[]> {
  return of([]);
}

Request payloads

먼저 (TypeScript를 사용하는 경우), DTO (Data Transfer Object) 스키마를 결정

DTO:
데이터가 네트워크를 통해 전송되는 방식을 정의하는 객체

  • TypeScript 인터페이스나 간단한 클래스를 사용하여 DTO 스키마를 정의

이때, 클래스를 사용하는 것을 권장

  • JavaScript ES6 표준의 일부이며, 컴파일된 JavaScript에서도 실제 엔티티로 유지
  • 반면 TypeScript 인터페이스는 트랜스파일 과정에서 제거되므로, Nest는 런타임에 이를 참조할 수 없음
  • 이는 Pipes와 같은 기능이 런타임에 변수의 메타타입에 접근해야 하기 때문에 중요
export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  return 'This action adds a new cat';
}

우리의 ValidationPipe는 메서드 핸들러가 받아들이지 않아야 하는 속성을 걸러낼 수 있습니다. 허용 가능한 속성을 화이트리스트에 추가하면, 화이트리스트에 포함되지 않은 속성은 자동으로 결과 객체에서 제거됩니다. CreateCatDto 예제에서는, 화이트리스트에 name, age, breed 속성이 포함됩니다. 자세히 알아보려면 여기를 참조하세요.

Handling errors

예외 처리 챕터에서 다룸

Getting up and running

정의 여부에 상관없이, Nest는 아직 CatsController가 존재한다는 것을 알지 못하므로 이 클래스를 인스턴스화하지 않음

Controller는 항상 모듈에 속함
따라서 @Module() 데코레이터 내의 controllers 배열에 포함
아직 루트 AppModule 외의 다른 모듈을 정의하지 않았으므로, CatsController를 도입하기 위해 이를 사용:

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

@Module({
  controllers: [CatsController],
})
export class AppModule {}
  • @Module() 데코레이터를 사용하여 모듈 클래스에 메타데이터를 첨부

Library-specific approach

라이브러리 특정 응답 객체를 사용해서 처리하는 방법

  • 특정 응답 객체를 주입하려면 @Res() 데코레이터를 사용
import { Controller, Get, Post, Res, HttpStatus } from '@nestjs/common';
import { Response } from 'express';

@Controller('cats')
export class CatsController {
  @Post()
  create(@Res() res: Response) {
    res.status(HttpStatus.CREATED).send();
  }

  @Get()
  findAll(@Res() res: Response) {
     res.status(HttpStatus.OK).json([]);
  }
}

이 접근 방식은 덜 명확하며 몇 가지 단점이 있음

  • 주요 단점은 코드가 플랫폼에 종속
  • 테스트가 더 어려워짐

또한, Interceptors와 @HttpCode() / @Header() 데코레이터와 같은 Nest 기능과의 호환성 상실

  • passthrough 옵션 true로 해결:
@Get()
findAll(@Res({ passthrough: true }) res: Response) {
  res.status(HttpStatus.OK);
  return [];
}
profile
학습 velog

0개의 댓글