Controlers

Controllers

컨트롤러는 들어오는 requests을 처리하고 responses을 클라이언트에게 반환합니다..

컨트롤러의 목적은 응용 프로그램에 대한 특정 요청을받는 것입니다. routing 메커니즘은 어떤 컨트롤러가 어떤 요청을 받는지 제어합니다. 종종 각 컨트롤러에는 하나 이상의 경로가 있으며 다른 경로는 다른 작업을 수행할 수 있습니다.

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

Routing

다음 예에서는 기본 컨트롤러를 정의하기 위해 필수@Controller() 데코레이터를 사용합니다. 선택적 경로 경로 접두사 cats를 지정합니다. @Controller()데코레이터에서 경로 접두사를 사용하면 관련된 경로 세트를 쉽게 그룹화하고 반복 코드를 최소화 할 수 있습니다. 예를 들어 /customers경로에서 고객 엔티티와의 상호 작용을 관리하는 경로 세트를 그룹화하도록 선택할 수 있습니다. 이 경우@Controller()데코레이터에서 경로 접두어 customers를 지정할 수 있으므로 파일의 각 경로에 대해 경로의 해당 부분을 반복 할 필요가 없습니다.

@@filename(cats.controller)
import { Controller, Get } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}
@@switch
import { Controller, Get } from '@nestjs/common';

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

info 힌트 CLI를 사용하여 컨트롤러를 만들려면 간단히$ nest g controller cats 명령을 실행하십시오.

findAll()메소드 앞에있는 @Get() HTTP 요청 메소드 데코레이터는 Nest에게 HTTP 요청에 대한 특정 엔드 포인트에 대한 핸들러를 작성하도록 지시합니다. 엔드 포인트는 HTTP 요청 방법 (이 경우 GET) 및 라우트 경로에 해당합니다. 라우트 경로는 무엇입니까? 핸들러의 라우트 경로는 제어기에 선언된 (선택적) 접두부와 요청 데코레이터에 지정된 경로를 연결하여 결정됩니다. 모든 경로에 대한 접두사 (cats)를 선언하고 데코레이터에 경로 정보를 추가하지 않았기 때문에 Nest는 GET/cats 요청을 이 핸들러에 매핑합니다. 언급 한 바와 같이, 경로에는 선택적 컨트롤러 경로 접두사 요청 메소드 데코레이터에 선언된 경로 문자열이 모두 포함됩니다. 예를 들어, 데코레이터 @Get('profile')과 결합된 customers의 경로 접두사는 GET/customers/profile과 같은 요청에 대한 경로 매핑을 생성합니다.

위의 예제에서 GET 요청이 이 엔드 포인트에 이루어지면 Nest는 요청을 사용자 정의 findAll()메소드로 라우팅합니다. 여기서 선택한 메소드 이름은 완전히 임의적입니다. 경로를 바인딩 할 메소드를 선언해야 하지만 Nest는 선택한 메소드 이름에 아무런 의미도 부여하지 않습니다. 이 메소드는 200 상태 코드 및 연관된 응답 (이 경우 문자열)을 리턴합니다. 왜 그런 일이 발생합니까? 설명하기 위해 먼저 Nest가 응답을 조작하기 위해 두 가지 다른 옵션을 사용한다는 개념을 소개합니다.

표준 (권장)

이 내장 메소드를 사용하여 요청 핸들러가 JavaScript 오브젝트 또는 배열을 리턴하면 자동으로 JSON으로 직렬화됩니다. 그러나 문자열을 반환하면 Nest는 시도하지 않고 문자열 만 보냅니다. 직렬화하십시오. 따라서 응답 처리가 간단 해집니다. 값을 반환하면 Nest가 나머지를 처리합니다. 또한 201을 사용하는 POST 요청을 제외하고 응답의 Status code는 기본적으로 항상 200입니다. @HttpCode(...)핸들러 수준의 데코레이터를 추가하여이 동작을 쉽게 변경할 수 있습니다. (Status codes를 보세요).

라이브러리별 옵션

라이브러리 별 (예: Express) 응답 객체를 사용할 수 있습니다. 메소드 핸들러 서명의 @Res() 데코레이터 (예: findAll(@Res() response))를 사용하여 삽입할 수 있습니다. 이 접근 방식을 사용하면 해당 객체에 의해 노출된 기본 응답 처리 방법을 사용할 수 있습니다 (및 책임). 예를 들어 Express를 사용하면 response.status(200).send ()와 같은 코드를 사용하여 응답을 구성 할 수 있습니다.

warning 경고 두 가지 방법을 동시에 사용할 수는 없습니다. Nest는 핸들러가 @Res() 또는 @Next()를 사용하는 시기를 감지하여 라이브러리별 옵션을 선택했음을 나타냅니다. 두 방법을 동시에 사용하는 경우 이 단일 경로에 대해 표준 방법이 자동으로 비활성화 되어 더 이상 예상대로 작동하지 않습니다.

Request object

핸들러는 종종 클라이언트 요청 세부 사항에 액세스해야합니다. Nest는 기본 플랫폼의 요청 객체에 대한 액세스를 제공합니다 (기본적으로 Express). 처리기의 서명에 @Req() 데코레이터를 추가하여 Nest에게 요청하도록 요청 객체에 액세스 할 수 있습니다.

@@filename(cats.controller)
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';
  }
}
@@switch
import { Controller, Bind, Get, Req } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  @Bind(Req())
  findAll(request) {
    return 'This action returns all cats';
  }
}

info 힌트 express 타이핑을 이용하려면 (위의 request: Request 파라미터 예제와 같이) @types/express 패키지를 설치하십시오.

요청 개체는 HTTP 요청을 나타내며 요청 쿼리 문자열, 매개 변수, HTTP 헤더 및 본문에 대한 속성을 갖습니다 (더 자세한 내용은 여기). 대부분의 경우 이러한 속성을 수동으로 가져올 필요는 없습니다. @Body() 또는 @Query()와 같은 전용 데코레이터를 대신 사용할 수 있습니다. 아래는 제공된 데코레이터와 이들이 나타내는 일반 플랫폼 별 오브젝트 목록입니다.

@Request()

req

@Response()

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]

*위의 라이브러리 별 섹션에서 언급했듯이 Response 객체에는 두 가지 종류가 있습니다. standard one은@Response() 데코레이터로 액세스합니다. @Res() 데코레이터를 사용하여 기본 네이티브 플랫폼 Response객체에 액세스 할 수 있습니다. 해당 섹션을 검토하여 차이점을 이해하십시오.

info 힌트 나만의 커스텀 데코레이터를 만드는 방법을 배우려면 여기 장을 방문하십시오.

Resources

이전에는 Cats 자원을 가져 오는 엔드 포인트를 정의했습니다 (GET 경로). 또한 일반적으로 새 레코드를 작성하는 엔드 포인트를 제공하려고합니다. 이를 위해 POST 핸들러를 만듭니다.

@@filename(cats.controller)
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';
  }
}
@@switch
import { Controller, Get, Post } from '@nestjs/common';

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

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

그렇게 간단합니다. Nest는 나머지 표준 HTTP 요청 엔드 포인트 데코레이터를 동일한 방식으로 제공합니다 - @Put(), @Delete(), @Patch(), @Options(), @Head()@All() 각각은 해당 HTTP 요청 방법을 나타냅니다.

Route wildcards

패턴 기반 경로도 지원됩니다. 예를 들어 별표는 와일드 카드로 사용되며 모든 문자 조합과 일치합니다.

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

'ab*cd'라우트 경로는 abcd, ab_cd, abecd 등과 일치합니다. ?, +, *()문자는 라우트 경로에 사용될 수 있으며 정규 표현식의 하위 집합입니다. 하이픈 (-)과 점 (.)은 문자 그대로 문자열 기반 경로로 해석됩니다.

Status code

언급 한대로 Status Code라는 응답은 201 인 POST 요청을 제외하고 기본적으로 항상 200입니다. 핸들러 레벨에서 @HttpCode(...)데코레이터를 추가하여이 동작을 쉽게 변경할 수 있습니다.

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

상태 코드는 정적이지 않지만 다양한 요인에 따라 달라집니다. 이 경우, 라이브러리 특정 Response을 사용할 수 있습니다 ( @Res() 객체를 사용하여 주입) (또는 오류가 발생한 경우 예외 발생).

Headers

커스텀 응답 헤더를 지정하기 위해 @Header()데코레이터 또는 라이브러리 특정 응답 객체를 사용하고res.header()를 직접 호출 할 수 있습니다.

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

Route parameters

정적 경로가있는 경로는 요청의 일부로 동적 데이터를 수락해야하는 경우 작동하지 않습니다 (예: GET /cats/1)를 사용하여 ID가 1 인 Cat을 가져옵니다. 매개 변수로 경로를 정의하기 위해 라우트 경로에 경로 매개 변수 토큰을 추가하여 요청 URL의 해당 위치에서 동적 값을 캡처 할 수 있습니다. 아래의 @Get()데코레이터 예제에서 경로 매개 변수 토큰은 이 사용법을 보여줍니다. 이 방법으로 선언 된 경로 매개 변수는 메소드 서명에 추가되어야 하는 @Param()데코레이터를 사용하여 액세스 할 수 있습니다.

@@filename()
@Get(':id')
findOne(@Param() params): string {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}
@@switch
@Get(':id')
@Bind(Param())
findOne(params) {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}

@Param()은 메소드 매개 변수 (위의 예제에서 params)를 장식하는 데 사용되며 route 매개 변수를 메소드 본문 내에서 장식 된 메소드 매개 변수의 특성으로 사용 가능하게 합니다. 위 코드에서 알 수 있듯이 params.id를 참조하여 id 매개 변수에 액세스 할 수 있습니다. 특정 매개 변수 토큰을 데코레이터에 전달한 다음 메서드 본문에서 이름으로 직접 경로 매개 변수를 참조 할 수도 있습니다.

@@filename()
@Get(':id')
findOne(@Param('id') id): string {
  return `This action returns a #${id} cat`;
}
@@switch
@Get(':id')
@Bind(Param('id'))
findOne(id) {
  return `This action returns a #${id} cat`;
}

Routes order

경로 등록 순서 (각 경로의 방법이 클래스에 표시되는 순서)가 중요합니다. 식별자 (cats/:id)로 Cat를 반환하는 경로가 있다고 가정합니다. 모든 cats를 한 번에 (cats) 리턴하는 클래스 정의에서 다른 엔드 포인트를 아래에 등록하면 , GET /cats 요청은 모든 경로 매개 변수가 선택적이므로 원하는 두 번째 핸들러에 절대 도달하지 않습니다. 다음 예를 참조하십시오.

@Controller('cats')
export class CatsController {
  @Get(':id')
  findOne(@Param('id') id: string) {
    return `This action returns a #${id} cat`;
  }

  @Get()
  findAll() {
    // This endpoint will never get called
    // because the "/cats" request is going
    // to be captured by the "/cats/:id" route handler
  }
}

이러한 부작용을 피하려면 findAll()선언 (데코레이터 포함)을 findOne()위로 옮기십시오.

Scopes

다른 프로그래밍 언어 배경을 가진 사람들의 경우 Nest에서 거의 모든 것이 들어오는 요청에서 공유된다는 것을 배우는 것은 예상치 못한 일입니다. 우리는 데이터베이스에 대한 연결 풀, 전역 상태를 가진 싱글 톤 서비스 등을 가지고 있습니다. Node.js는 모든 요청이 별도의 스레드에 의해 처리되는 요청/응답 다중 스레드 상태 비 저장 모델을 따르지 않습니다. 따라서 싱글 톤 인스턴스를 사용하는 것은 애플리케이션에 안전합니다.

그러나 컨트롤러의 요청 기반 수명이 원하는 동작, 예를 들어 GraphQL 애플리케이션의 요청 별 캐싱, 요청 추적 또는 다중 테넌시가 될 수있는 경우가 있습니다. 여기 범위를 제어하는 방법에 대해 알아보십시오.

Asynchronicity

우리는 현대 JavaScript를 좋아하며 데이터 추출이 대부분 비동기라는 것을 알고 있습니다. 이것이 Nest가 async기능을 지원하고 잘 작동하는 이유입니다.

info 힌트 async / await 기능에 대해 자세히 알아보기 여기

모든 비동기 함수는 Promise를 반환해야 합니다. 즉, Nest가 자체적으로 해결할 수 있는 지연된 값을 리턴 할 수 있습니다. 이것의 예를 보겠습니다:

@@filename(cats.controller)
@Get()
async findAll(): Promise<any[]> {
  return [];
}
@@switch
@Get()
async findAll() {
  return [];
}

위의 코드는 완전히 유효합니다. 또한 Nest 경로 처리기는 RxJS 관측 가능한 스트림을 반환할 수 있어 훨씬 강력합니다. Nest는 아래의 소스를 자동으로 구독하고 마지막으로 방출된 값을 취합니다 (스트림이 완료되면).

@@filename(cats.controller)
@Get()
findAll(): Observable<any[]> {
  return of([]);
}
@@switch
@Get()
findAll() {
  return of([]);
}

위의 두 가지 방법 모두 작동하며 요구 사항에 맞는 것을 사용할 수 있습니다.

Request payloads

POST 경로 처리기의 이전 예에서는 클라이언트 매개 변수를 허용하지 않았습니다. 여기에 @Body() 데코레이터를 추가하여 이 문제를 해결하겠습니다.

그러나 먼저 (TypeScript를 사용하는 경우) DTO (데이터 전송 개체) 스키마를 결정해야 합니다. DTO는 네트워크를 통해 데이터가 전송되는 방식을 정의하는 개체입니다. TypeScript 인터페이스를 사용하거나 간단한 클래스를 사용하여 DTO 스키마를 결정할 수 있습니다. 흥미롭게도 여기서 클래스 를 사용하는 것이 좋습니다. 왜? 클래스는 JavaScript ES6 표준의 일부이므로 컴파일 된 JavaScript에서 실제 엔티티로 유지됩니다. 반면, 변환 과정에서 TypeScript 인터페이스가 제거되므로 Nest는 런타임시 인터페이스를 참조 할 수 없습니다. 파이프 와 같은 기능은 런타임에 변수의 메타 타입에 액세스 할 때 추가 가능성을 가능하게 하기 때문에 중요합니다.

CreateCatDto 클래스를 만들어 봅시다 :

@@filename(create-cat.dto)
export class CreateCatDto {
  readonly name: string;
  readonly age: number;
  readonly breed: string;
}

세 가지 기본 속성만 있습니다. 그 후에 우리는 CatsController 안에서 새로 생성된 DTO를 사용할 수 있습니다:

@@filename(cats.controller)
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  return 'This action adds a new cat';
}
@@switch
@Post()
@Bind(Body())
async create(createCatDto) {
  return 'This action adds a new cat';
}

Handling errors

오류 처리 (예: 예외 작업) 여기에 대한 별도의 장이 있습니다.

Full resource sample

아래는 사용 가능한 여러 데코레이터를 사용하여 기본 컨트롤러를 만드는 예입니다. 이 컨트롤러는 내부 데이터에 액세스하고 조작하는 몇 가지 방법을 제공합니다.

@@filename(cats.controller)
import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common';
import { CreateCatDto, UpdateCatDto, ListAllEntities } from './dto';

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

  @Get()
  findAll(@Query() query: ListAllEntities) {
    return `This action returns all cats (limit: ${query.limit} items)`;
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return `This action returns a #${id} cat`;
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
    return `This action updates a #${id} cat`;
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return `This action removes a #${id} cat`;
  }
}
@@switch
import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common';

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

  @Get()
  @Bind(Query())
  findAll(@Query() query) {
    return `This action returns all cats (limit: ${query.limit} items)`;
  }

  @Get(':id')
  @Bind(Param('id'))
  findOne(@Param('id') id) {
    return `This action returns a #${id} cat`;
  }

  @Put(':id')
  @Bind(Param('id'), Body())
  update(@Param('id') id, @Body() updateCatDto) {
    return `This action updates a #${id} cat`;
  }

  @Delete(':id')
  @Bind(Param('id'))
  remove(@Param('id') id) {
    return `This action removes a #${id} cat`;
  }
}

Getting up and running

위의 컨트롤러가 완전히 정의된 상태에서 Nest는 여전히 CatsController가 존재한다는 것을 알지 못하므로 결과적으로 이 클래스의 인스턴스를 만들지 않습니다.

컨트롤러는 항상 모듈에 속하므로 @Module() 데코레이터 내에 컨트롤러 배열을 포함시킵니다. 루트AppModule을 제외한 다른 모듈은 아직 정의하지 않았으므로 이를 사용하여 CatsController를 소개합니다.

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

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

우리는@Module()데코레이터를 사용하여 메타 데이터를 모듈 클래스에 첨부했으며 Nest는 이제 어떤 컨트롤러를 마운트해야 하는지 쉽게 반영 할 수 있습니다.

Appendix: Library-specific approach

지금까지 우리는 Nest 표준 응답 조작 방법에 대해 설명했습니다. 응답을 조작하는 두 번째 방법은 라이브러리 별 응답 객체를 사용하는 것입니다. 특정 응답 객체를 주입하려면 @Res() 데코레이터를 사용해야 합니다. 차이점을 보여주기 위해 CatsController를 다음과 같이 다시 작성해 봅시다 :

@@filename()
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([]);
  }
}
@@switch
import { Controller, Get, Post, Bind, Res, Body, HttpStatus } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Post()
  @Bind(Res(), Body())
  create(res, createCatDto) {
    res.status(HttpStatus.CREATED).send();
  }

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

이 방법은 효과가 있으며 실제로 응답 객체 (헤더 조작, 라이브러리 별 기능 등)를 완벽하게 제어하여 더 많은 유연성을 허용하지만 주의해서 사용해야합니다. 일반적으로 이 접근 방식은 훨씬 덜 명확하며 몇 가지 단점이 있습니다. 주요 단점은 인터셉터 및 @HttpCode()데코레이터와 같은 Nest 표준 응답 처리에 의존하는 Nest 기능과의 호환성이 손실된다는 것입니다. 또한 코드는 플랫폼에 따라 달라지며 (기본 라이브러리는 응답 객체에 대해 다른 API를 가질 수 있음) 테스트하기가 더 어렵습니다 (응답 객체를 속여야 함).

결과적으로 가능한 경우 Nest 표준 접근 방식이 항상 선호됩니다.

Last updated