Controllers
컨트롤러는 수신된 요청(request)를 처리하고 클라이언트에 응답(response)를 반환할 책임이 있다.
컨트롤러의 목적은 애플리케이션에 대한 특정 요청을 수신하는 것이다. 라우팅 매커니즘은 어떤 컨트롤러가 어떤 요청을 수신할지 제어한다.각 컨트롤러에는 둘 이상의 라우트가 있는 경우가 많으며, 각 라우트마다 다른 작업을 수행할 수 있다.
기본 컨트롤러를 생성하기 위해 클래스와 데코레이터를 사용한다. 데코레이터는 클래스를 필수 메타데이터와 연결하고 Nest가 라우팅 맵(요청을 그에 상응하는 컨트롤러에 연결한다)을 생성할 수 있도록 한다.
HINT
유효성 검사 기능이 내장된 CRUD 컨트롤러를 빠르게 만드려면 CLI의 CRUD 생성기를 사용할 수 있다.
예시) nest g resource [name]
Routing
다음 예제에서는 기본 컨트롤러를 정의하는데 필요한 @Controller() 데코레이터를 사용한다. 선택적 라우팅 경로 접두사(prefix)로 cats을 지정할 것이다. @Controller() 데코레이터 경로 접두사를 사용하면 관련 경로 집합을 쉽게 그룹화하고 반복되는 코드를 최소화할 수 있다. 예를들어 cat 엔티티와의 상호작용을 관리하는 일련의 경로를 /cats 경로로 그룹화할 수 있다. 이 경우 @Controller() 데코레이터에 경로 접두사 cats을 지정하면 각 파일의 경로에서 해당 부분을 반복할 필요가 없어진다.
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
HINT
CLI를 사용하여 컨트롤러를 만들려면 아래 명령어를 실행하면 된다
$ nest g controller [name]
위의 예제에서해당 cats 엔드포인트로 GET 요청이 이루어지면 Nest는 요청을 findAll() 메서드로 라우팅한다. 메소드 이름은 완전히 임의적이며, 반드시 라우트에 해당하는 메서드를 연결해야하지만, Nest는 메서드 이름에는 어떠한 의미도 붙이지 않는다.
위의 메서드는 200 상태코드와 문자열만 있는 응답을 반환한다. 왜 이런일이 발생할까? 설명을 위해 Nest가 응답을 조작하는 데 두가지 다른 옵션을 사용한다는 개념을 알아야 한다.
Standard (recommended) | 기본 제공 메서드를 사용하면 요청 핸들러가 자바스크립트 객체나 배열을 반환할 경우 자동으로 JSON으로 직렬화한다. 그러나 자바스크립트 원시값(문자열, 숫자, 불리언)을 반환하는 경우 Nest는 직렬화를 시도하지 않고 값만 전송한다. 따라서 응답 처리가 간단해진다. 값만 반환하면 나머지는 Nest가 알아서 처리한다. 또한 응답의 상태코드는 201을 사용하는 POST를 제외하고는 기본적으로 항상 200이다. 핸들러 수준에서 @HttpCode(...) 데코레이터를 사용하여 상태코드를 쉽게 변경할 수 있다. |
Library-specific | 라이브러리별 응답 객체를 사용할 수 있으며, 메서드 핸들러 시그니처에 @Res() 데코레이터를 사용하여 삽입할 수 있다. 예시) findAll(@Res() response 이 접근 방식을 사용하면 해당 객체에 의해 노출된 기본 응답 처리 메서드를 사용할 수 있다. 예를 들어 Express에서는 response.status(200).send()와 같은 코드를 사용하여 응답을 구성할 수 있다. |
WARNING
Nest는 핸들러가 @Res() 또는 @Next()를 사용하는 경우를 감지하여 라이브러리 별 옵션을 선택했음을 나타낸다. 두 가지 접근 방식을 동시에 사용하면 이 단일 경로에 대해 표준 접근 방식이 자동으로 비활성화되고 더 이상 예상대로 작동하지 않는다. 두 접근 방식을 동시에 사용하려면 (예: 응답 객체를 삽이하여 쿠키/헤더만 설정하고 나머지는 프레임워크에 맡기는 경우) @Res({ passthrough: true }) 데코레이터에서 passthrough 옵션을 true로 설정해야 한다.
Request object
핸들러는 종종 클라이언트 요청 세부 정보에 엑세스해야 한다. 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';
}
}
// 요청 매개변수에서 예제와 같이 익스프레스 타이핑을 활용하려면
// @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()의 별칭일 뿐이다. 둘 다 기본 네이티브 플랫폼 응답 객체 인터페이스를 직접 노출한다. 이를 사용할때는 기본 라이브러리의 타이핑(@types/express)도 가져와야만 제대로 활용할 수 있다.
메서드 핸들러에 @Res() 또는 @Response()를 삽입하면 해당 핸들러에 대한 Nest를 라이브러리 전용 모드로 전환하고 응답을 관리할 책임이 생긴다는 점에 유의하라. 이 경우 응답 객체 (예시: res.json() 또는 res.send()를 호출하여 일종의 응답을 발행해야 하며, 그렇지 않으면 HTTP 서버가 중단된다.
Asynchronicity
최신 자바스크립트의 데이터 추출은 대부분 비동기식이다. 그렇기 때문에 Nest도 비동기 함수를 지원하고, 잘 작동한다.
모든 비동기 함수는 프로미스를 반환해야 한다. 즉 Nest가 자체적으로 해결할 수 있는 지연된 값을 반환할 수 있다.
@Get()
async findAll(): Promise<any[]> {
return [];
}
위의 코드는 완전히 유효하다. 또한 Nest 라우트 핸들러는 RxJS observable streams을 반환할 수 있어 훨씬 더 강력하다. Nest는 아래 소스를 자동으로 구독하고 스트림이 완료되면 마지막으로 방출된 값을 가져온다.
@Get()
findAll(): Observable<any[]> {
return of([]);
}
위 두가지 방법 모두 효과가 있으며, 요구사항에 맞는 방법을 사용할 수 있다.
Full resource sample
아래는 사용 가능한 몇 가지 데코레이터를 사용하여 기본 컨트롤러를 만드는 예제이다. 이 컨트롤러는 내부 데이터에 엑세스하고 조작하는 몇 가지 메서드를 노출한다.
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`;
}
}
Getting up and running
위의 컨트롤러가 완전히 정의되었지만 Nest는 여전히 CatsController가 존재한다는 것을 알지 못하므로 이 클래스의 인스턴스를 생성하지 않는다.
컨트롤러는 항상 모듈에 속하기 때문에 @Module() 데코레이터 안에 컨트롤러 배열을 포함시킨다. 아직 AppModules를 제외한 다른 모듈을 정의하지 않았으므로 이를 사용하여 CatsController를 장착한다.
// app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
@Module({
controllers: [CatsController],
})
export class AppModule {}
@Module() 데코레이터를 사용하여 모듈 클래스에 메타데이터를 첨부했고, 이제 Nest는 어떤 컨트롤러를 마운트해야 하는지 쉽게 반영할 수 있다.
공식문서에서 참조할 수 있는 자료들
이 외에도 공식문서에서 다음과 같은 레퍼런스를 참조할 수 있다.
- Resource : HTTP 메서드에 관한 레퍼런스
- Route wildcards : 'ab*cd' 등 와일드카드가 추가되는 패턴 기반의 라우트 관리
- Status Code : 응답에 관한 HttpCode를 바꾸는 기능에 관한 레퍼런스
- Headers : 커스텀 응답 헤더를 만드는 것에 대한 레퍼런스
- Redirection : 응답을 특정 URL로 리다이렉션하는 방법에 관한 레퍼런스
- Route parameters : cats/:id 와 같이 매개변수의 역할을 지닌 라우트를 관리하는 방법
- Sub-Domain Routing : 요청의 HTTP 호스트가 특정 값과 일치하는지 확인이 가능함
- Scopes : GraphQL 요청 등 특수한 상황일 경우에 참고해야하는 레퍼런스
- Request payloads : 요청의 Body값을 추출할 수 있는 방법 및 DTO 타입을 정하는 것에 대한 레퍼런스
- Handling errors : exceptions 등을 활용해 에러처리를 효과적으로 하는 방법에 대한 레퍼런스
- Library-specific approach : Nest 표준 방법이 아닌 라이브러리별 응답 객체를 사용하는 방법에 대한 레퍼런스
'NestJS' 카테고리의 다른 글
Modules (0) | 2024.04.17 |
---|---|
Providers (1) | 2024.04.17 |
First steps (0) | 2024.04.17 |
Introduction (0) | 2024.04.17 |