컨트롤러는 들어오는 요청을 처리하고 클라이언트에 응답을 반환하는 일을 담당한다 .
구조
애플리케이션은 특정 요청을 처리하기 위해 컨트롤러를 필요로 한다, 라우팅 메커니즘은 어떤 컨트롤러가 어떤 요청을 수신하는지 제어하고, 각 컨트롤러는 둘 이상의 경로가 있는 경우가 많으며, 서로 다른 경로가 다른 작업을 수행한다.
기본 컨트롤러를 생성하기 위해 클래스와 데코레이터를 사용한다. 데코레이터는 클래스를 필수 메타데이터와 연결하고 요청을 해당 컨트롤러에 연결하는 기능을 한다.
CRUD 생성기를 사용하면 빠르게 컨트롤러를 생성할 수 있다.
# 예) nest g resource cats
nest g resource {리소스명(통상 모델명)}
라우팅
@Controller() 데코레이터를 class 선언위에 경로 접두사로 지정할 수 있다. 관련 경로 집합을 쉽게 그룹화하고 반복 코드를 최소화 할 수 있다.
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 cats
nest g controller {컨트롤러명}
위의 예에서 이 엔드포인트에 GET 요청이 이루어지면 Nest는 요청을 사용자 정의 findAll()메서드로 라우팅한다. 여기서 선택한 메소드 이름은 완전히 임의적이다. 분명히 경로를 바인딩할 메서드를 선언해야 하지만 Nest는 선택한 메서드 이름에 아무런 의미를 부여하지 않는다. 이 메소드는 200 상태코드 관련 응답을 반환하고, 위의 경우에는 문자열이다. Nest가 응답을 조작하기 위해 두가지 다른 옵션을 제공한다.
표준 (권장) |
이 내장 메소드를 사용하면 요청 핸들러가 JavaScript 객체 또는 배열을 반환할 때 자동으로 JSON으로 직렬화된다. 그러나 JavaScript 기본 유형(예: string, number, boolean)을 반환하는 경우 Nest는 직렬화를 시도하지 않고 값만 보냅니다. 이렇게 하면 응답 처리가 간단해집니다. 값만 반환하면 Nest가 나머지를 처리한다. 또한 응답의 상태 코드는 201을 사용하는 POST 요청을 제외하고 기본적으로 항상 200입니다. @HttpCode(...)핸들러 수준에서 데코레이터를 추가하여 상태코드를 쉽게 변경할 수 있다. 상태 코드 참조 ). |
라이브러리 종속적 | 메소드 핸들러 시그니처에서 @Res() 데코레이터를 사용하여 주입할 수 있는 라이브러리별 (예: Express) 응답 객체를 사용할 수 있다(예: findAll(@Res() response) . 이 접근 방식을 사용하면 해당 개체에 의해 노출된 기본 응답 처리 메서드를 사용할 수 있다. 예를 들어 Express를 사용하면 다음과 같은 코드( response.status(200).send() )를 사용하여 응답을 구성할 수 있다 |
주의) Nest는 핸들러가 @Res(), @Next() 등을 사용하는 경우, 라이브러리별 옵션을 선택했음을 나타낸다. 두 접근 방식을 동시에 사용하는 경우 이 단일 경로에 대해 표준 접근 방식이 자동으로 비활성화되고 더 이상 예상대로 작동하지 않는다. 두 가지 접근 방식을 동시에 사용하려면(예: 응답 객체를 삽입하여 쿠키/헤더만 설정하고 나머지는 프레임워크에 남겨 두는 방식) @Res({ passthrough: true }) 데코레이터에서 passthrough 옵션을 true 로 설정해야 한다.
요청 객체
핸들러는 클라이언트 요청 세부정보에 액세스해야 하는 경우가 많다. Nest는 기본 플랫폼(기본적으로 Express)의 요청 객체 에 대한 액세스를 제공한다 @Req() 데코레이터를 추가하여 Nest에 요청 객체를 삽입하도록 지시하여 요청 객체에 액세스할 수 있다.
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';
}
}
요청 개체는 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 |
앞서 우리는 cats 리소스( GET 경로)를 가져오기 위한 엔드포인트를 정의했다. 또한 일반적으로 새 레코드를 생성하는 엔드포인트를 제공하기 위해 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 데코레이터는 이를 모두 처리하는 엔드포인트를 정의한다.
라우트 와일드카드
패턴 기반 경로도 지원되는데, 예를 들어 별표는 와일드카드로 사용되며 모든 문자 조합과 일치한.
@Get('ab*cd')
findAll() {
return 'This route uses a wildcard';
}
주의) 경로중간의 와일드 카드는 Express에서만 지원된다.
상태코드
언급한 대로 응답 상태 코드는 201 인 POST 요청을 제외하고 기본적으로 항상 200 이다 . 핸들러 수준에서 데코레이터를 추가하면 이 동작을 쉽게 변경할 수 있다.
@Post()
@HttpCode(204)
create() {
return 'This action adds a new cat';
}
힌트) HttpCode 는 @nestjs/common 패키지에서 가져온다.
헤더
사용자 정의 응답 헤더를 지정하려면 @Header() 데코레이터를 사용한다, 라이브러리 종속적 응답 객체를 직접 사용하려면 res.header() 를 호출한다.
@Post()
@Header('Cache-Control', 'none')
create() {
return 'This action adds a new cat';
}
힌트) Header 는 @nestjs/common 패키지에서 가져온다.
리디렉션
응답을 특정 URL로 리디렉션하려면 @Redirect() 데코레이터 사용하거나, 라이브러리별 응답 개체를 사용하기 위해 res.redirect() 를 직접 호출할 수 있다.
@Redirect() 는 두 개의 인수를 사용하며 url, statusCode 둘 다 선택 사항입니다. 생략할 경우 statusCode의 기본값은 302 (Found) 이다.
@Get()
@Redirect('https://nestjs.com', 301)
힌트) 때로는 HTTP 상태 코드나 리디렉션 URL을 동적으로 확인해야 할 수도 있다. HttpRedirectResponse 객체를 반환하여 이를 수행한다.
반환된 값은 @Redirect() 데코레이터에 전달된 모든 인수를 재정의한다. 예를 들면 아래코드와 같다
@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
if (version && version === '5') {
return { url: 'https://docs.nestjs.com/v5/' };
}
}
라우트 파라미터
요청의 일부로 동적 데이터를 허용해야 하는 경우 (예: GET /cats/1 id로 1번째 cat 가져오기) 정적 경로가 있는 경로는 작동하지 않는다. 매개변수로 경로를 정의하기 위해 경로에 경로 매개변수 토큰을 추가하여 요청 URL의 해당 위치에서 동적 값을 캡처할 수 있다. 아래 데코레이터 @Get() 예제 의 경로 매개변수 토큰은 이러한 사용법을 보여준다. 이런 방식으로 선언된 경로 매개변수는메소드 시그니처에 추가되어야 하는 @Param() 데코레이터를 사용하여 액세스할 수 있다.
힌트) 매개변수가 있는 경로는 정적 경로 뒤에 선언되어야 한다. 이렇게 하면 매개변수화된 경로가 정적 경로로 향하는 트래픽을 가로채는 것을 방지할 수 있다.
@Get(':id')
findOne(@Param() params: any): string {
console.log(params.id);
return `This action returns a #${params.id} cat`;
}
@Param()params위의 예에서는 메
위의 예에서는 @Param() 데코레이터가 메서드 매개 변수 params를 장식하는 데 사용되며 경로 매개 변수를 메서드 본문 내에서 장식된 해당 메서드 매개 변수의 속성으로 사용할 수 있도록 만든다. 위의 코드에서 볼 수 있듯이 를 params.id 참조하여 매개변수에 액세스할 수 있다. 특정 매개변수 토큰을 데코레이터에 전달한 다음 메서드 본문에서 이름으로 직접 경로 매개변수를 참조할 수도 있다.
서브 도메인 라우팅
@Controller 데코레이터는 들어오는 요청이 HTTP 호스트가 특정 값과 일치하도록 요구할 수 있다.
@Controller({ host: 'admin.example.com' })
export class AdminController {
@Get()
index(): string {
return 'Admin page';
}
}
경고) Fastify 는 내장 라우터 지원이 약하다, 위 코드를 쓸때는 Express를 사용해야 한다.
route와 유사하게 hosts 옵션은 토큰을 사용하여 호스트 이름의 해당 위치에서 동적 값을 캡처할 수 있다. 아래 @Controller() 데코레이터 예제에서 @HostParam() 데코레이터를 사용하는 account 변수는 이러한 사용법을 보여준다.
@Controller({ host: ':account.example.com' })
export class AccountController {
@Get()
getInfo(@HostParam('account') account: string) {
return account;
}
}
스코프
다양한 프로그래밍 언어 배경을 가진 사람들의 경우 Nest에서 들어오는 요청 전반에 걸쳐 거의 모든 것이 공유된다는 사실이 예상치 못한 일일 수도 있다. 이러한 공유 자원에는 데이터베이스에 대한 연결 풀, 전역 상태를 갖는 싱글톤 서비스 등이 있다. Node.js는 모든 요청이 별도의 스레드에 의해 처리되는 요청/응답 다중 스레드 상태 비저장 모델을 따르지 않는다는 점을 기억해야 한다. 따라서 싱글톤 인스턴스를 사용하는 것은 우리 애플리케이션에 완전히 안전 하다.
그러나 컨트롤러의 요청 기반 수명이 제어되어야 하는 극단적인 경우가 있다(예: GraphQL 애플리케이션의 요청별 캐싱, 요청 추적 또는 다중 테넌시)
비동기성
우리는 최신 JavaScript를 좋아하며 데이터 추출이 대부분 비동기식 이라는 것을 알고 있다 . 이것이 바로 Nest가 async 기능을 지원하고 잘 작동하는 이유이다.
모든 비동기 함수는 Promise 입니다.. 이는 Nest가 지연된 값을 반환할 수 있음을 의미한다. 아래는 이에 대한 예제이다.
@Get()
async findAll(): Promise<any[]> {
return [];
}
위의 코드는 완전히 유효합니다. 또한 Nest 경로 핸들러는 RxJS 옵저버블 스트림을 반환할 수 있어 더욱 강력해졌다. Nest는 아래 소스를 자동으로 구독하고 마지막으로 내보낸 값을 가져온다(스트림이 완료되었을 때).
@Get()
findAll(): Observable<any[]> {
return of([]);
}
위의 두 접근 방식 모두 작동하며 요구 사항에 맞춰 알맞게 사용할 수 있다.
페이로드 요청
이전 POST 경로 처리기 예에서는 클라이언트 매개변수를 허용하지 않았습니다. 여기에 @Body() 데코레이터 를 추가하여 이 문제를 해결한다.
하지만 먼저(TypeScript를 사용하는 경우) DTO (Data Transfer Object) 스키마를 결정해야 한다 . DTO는 네트워크를 통해 데이터가 전송되는 방법을 정의하는 개체다. TypeScript 인터페이스나 간단한 클래스를 사용하여 DTO 스키마를 결정할 수 있다. 흥미롭게도 여기에서 클래스를 사용하는 것이 좋다 . 왜냐하면 클래스는 JavaScript ES6 표준의 일부이므로 컴파일된 JavaScript에서 실제 엔터티로 유지되기 때문이다. 반면에 TypeScript 인터페이스는 변환 중에 제거되므로 Nest는 런타임에 이를 참조할 수 없다. Pipes 와 같은 기능이 런타임에 변수의 메타타입에 액세스할 때 추가적인 가능성을 제공하기 때문에 기억해두자.
CreateCatDto 클래스를 만들어 보자.
export class CreateCatDto {
name: string;
age: number;
breed: string;
}
기본 속성은 세 가지뿐입니다. 그런 다음 새로 생성된 DTO를 다음 CatsControlle 내부에서 사용할 수 있다.
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
힌트) ValidationPipe 는 메소드 핸들러가 수신해서는 안 되는 속성을 필터링할 수 있다 . 이 경우 허용되는 속성을 화이트리스트에 추가할 수 있으며, 화이트리스트에 포함되지 않은 속성은 결과 개체에서 자동으로 제거된다. CreateCatDto 예에서 화이트리스트는 name, age및 breed 속성이다.
전체 리소스 샘플
다음은 기본 컨트롤러를 생성하기 위해 사용 가능한 여러 데코레이터를 사용하는 예제이다. 이 컨트롤러는 내부 데이터에 액세스하고 조작하는 몇 가지 방법을 제공한다.
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`;
}
}
시작 및 실행
위의 컨트롤러가 완전히 정의된 경우 Nest는 CatsController 가 존재하는지 알지 못하므로 결과적으로 이 클래스의 인스턴스를 생성하지 않는다.
컨트롤러는 항상 모듈에 속하므로 데코레이터 @Module() 내 controllers 배열 내에 포함되어야 한다.
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
@Module({
controllers: [CatsController],
})
export class AppModule {}
@Module() 데코레이터 를 사용하여 모듈 클래스에 메타데이터를 연결했으며, 이제 Nest는 어떤 컨트롤러를 마운트 하더라도 쉽게 반영할 수 있다.
라이브러리별 접근 방식
지금까지 응답을 조작하는 Nest 표준 방식에 대해 논의했다. 응답을 조작하는 두 번째 방법은 라이브러리별 응답 객체를 사용하는 것이다 . 특정 응답 객체를 주입하려면 @Res()데코레이터를 사용해야 한다. 차이점을 보여주기 위해 다음과 같이 CatsController 를 다시 작성해 보겠다.
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([]);
}
}
이 접근 방식은 효과가 있고 실제로 응답 개체에 대한 전체 제어(헤더 조작, 라이브러리 관련 기능 등)를 제공하여 어떤 면에서 더 많은 유연성을 허용하지만 주의해서 사용해야 한다. 일반적으로 이 접근 방식은 덜 명확하며 몇 가지 단점이 있다. 가장 큰 단점은 코드가 플랫폼에 종속되고(기본 라이브러리가 응답 개체에 대해 다른 API를 가질 수 있으므로) 테스트하기가 더 어렵다는 것이다.(응답 개체를 모의해야 하는 등).
또한 위의 예에서는 인터셉터 및 @HttpCode()/ @Header()데코레이터와 같은 Nest 표준 응답 처리에 의존하는 Nest 기능과의 호환성이 손실된다. 이 문제를 해결하려면 다음과 같이 passthrough옵션을 true로 설정하면 된다.
@Get()
findAll(@Res({ passthrough: true }) res: Response) {
res.status(HttpStatus.OK);
return [];
}
이제 기본 응답 개체와 상호 작용할 수 있지만(예: 특정 조건에 따라 쿠키 또는 헤더 설정) 나머지는 프레임워크에 맡길 수 있다.
'Backend(Framework) > NestJS 개요(공식문서 번역)' 카테고리의 다른 글
6. Exception filters (0) | 2023.11.17 |
---|---|
5. Middleware (1) | 2023.11.16 |
4. Modules (1) | 2023.11.16 |
3. Provider (1) | 2023.11.16 |
1. NestJS 시작 (0) | 2023.11.04 |