NestJS
Nest(NestJS)는 효율적이고 확장 가능한 Node.js 서버 측 애플리케이션을 구축하기 위한 프레임워크입니다 .
특징
- 프로그레시브 JavaScript를 사용하고 TypeScript 로 구축되어 완벽하게 지원
- OOP(객체 지향 프로그래밍), FP(기능 프로그래밍) 및 FRP(기능 반응 프로그래밍) 요소를 결합
- 내부적으로 Nest는 Express (기본값)와 같은 강력한 HTTP 서버 프레임워크를 사용하며 선택적으로 Fastify 도 사용하도록 구성할 수 있습니다!
- Nest는 이러한 일반적인 Node.js 프레임워크(Express/Fastify)보다 높은 수준의 추상화를 제공하지만 해당 API를 개발자에게 직접 공개
- 이를 통해 개발자는 기본 플랫폼에서 사용할 수 있는 수많은 타사 모듈을 자유롭게 사용할 수 있음
설정
$ npm i -g @nestjs/cli
$ nest new project-name
NestJS의 핵심 파일
app.controller.ts | 단일 경로가 있는 기본 컨트롤러입니다. |
app.controller.spec.ts | 컨트롤러에 대한 단위 테스트입니다. |
app.module.ts | 애플리케이션의 루트 모듈입니다. |
app.service.ts | 단일 방식의 기본 서비스입니다. |
main.ts | NestFactory핵심 기능을 사용하여 Nest 애플리케이션 인스턴스를 생성하는 애플리케이션의 항목 파일입니다 . |
Controller
NestJs는 MVC 패턴으로 이루어지며, 컨트롤러는 들어오는 요청을 처리하고 클라이언트에게 응답을 반환하는 역할
- 컨트롤러의 목적은 애플리케이션에 대한 특정 요청을 수신하는 것
- 라우팅 매커니즘은 요청에 따라 어떤 컨트롤러가 어떤 요청을 수신하는지 제어하는 역할
- 일반적으로 컨트롤러에는 둘 이상의 경로가 있는 경우가 많으며, 각 다른 경로와 다른 작업을 수행함
- 컨트롤러의 생성과 속성을 위해 데코레이터를 사용함
- 데코레이터는 클래스를 필수 메타데이터와 연결하고 Nest가 라우팅 맵을 생성할 수 있도록 함
프로바이더는 Nest의 기본 개념이다. 기본 Nest 클래스는 서비스, 레파지토리, 팩토리, 헬퍼 등 프로바이더로 취급될 수 있다. ( 모듈에서 프로바이더로 선언된 평범한 자바스크립트 클래스가 결국 Provider이다. )
프로바이더의 주요 아이디어는 의존성을 주입할 수 있다는 점인데, 객체가 서로 다양한 관계를 맺을 수 있고, 객체의 인스턴스를 연결해주는 기능은 Nest 런타임 시스템에 위임된다.
라우팅 ( @Controller() : 선택적 경로 접두사 )
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
- Nest에서 HTTP GET 요청 핸들러를 생성하기 전에 사용하는 데코레이터는 @Get()입니다. 이 데코레이터는 특정 엔드포인트에 대한 GET 요청을 처리하는 핸들러를 생성합니다.
- 핸들러의 경로는 데코레이터에 선언된 경로와 선택적 컨트롤러 경로 접두사를 결합하여 결정됩니다. 예를 들어, @Get('breed')와 같은 경우, 해당 핸들러는 GET /cats/breed 요청에 매핑됩니다.
- Nest에서 선택한 메소드의 이름[함수 본문]은 경로를 바인딩할 때 중요하지 않습니다. 따라서 메소드 이름은 임의적입니다. 해당 메소드는 200 상태 코드와 함께 응답을 반환합니다. 이는 해당 메소드가 문자열과 같은 JavaScript 기본 유형을 반환하기 때문입니다. Nest는 기본 유형을 반환할 때 JSON으로 직렬화하지 않고 값을 그대로 보냅니다.
- 응답의 상태 코드는 기본적으로 항상 200이며, @HttpCode(...) 데코레이터를 사용하여 변경할 수 있습니다.
- Nest는 응답을 조작하는 두 가지 다른 방법을 제공합니다.
- 표준 방식은 JavaScript 객체 또는 배열을 반환할 때 자동으로 JSON으로 직렬화하는 것
- ?
- 라이브러리별 방식은 주입할 수 있는 라이브러리별 응답 객체를 사용하여 응답을 구성하는 것
- 이 두 가지 방식을 동시에 사용할 경우, @Res() 또는 @Next() 데코레이터를 선택하여 나타내어야 합니다. 이를 통해 Nest에서 HTTP 요청 핸들러를 생성하고 응답을 처리하는
요청 객체 [ @Req() ]
핸들러는 클라이언트 요청 세부정보에 액세스해야 하는 경우가 많다. 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 |
기본 HTTP 플랫폼(예: Express 및 Fastify) 간의 타이핑과의 호환성을 위해 Nest는 @Res() 및 @Response() 데코레이터를 제공한다.
@Res()는 단순히 @Response()의 별칭으로 둘 다 기본 네이티브 플랫폼 응답 개체 인터페이스를 직접 노출함
사용할 때는 기본 라이브러리(예: @type/express)에 대한 타이핑도 가져와야 완전히 활용할 수 있음
메서드 핸들러에 @Res() 또는 @Response()를 주입할 때 해당 핸들러에 대해 Nest를 라이브러리 고유 모드로 전환하면 응답을 관리할 책임을 지게 됨이렇게 할 때는 응답 개체(예: res.json(...) 또는 res.send(...)를 호출하여 일종의 응답을 발행해야 합니다. 그렇지 않으면 HTTP 서버가 행해집니다
Nest.js에서는 HTTP 요청을 처리하는 핸들러에 @Res() 또는 @Response()를 주입하여 해당 핸들러를 라이브러리 고유 모드로 전환할 수 있습니다. 이는 Nest.js가 Express.js나 Fastify 같은 백엔드 라이브러리와 함께 사용될 때 발생합니다. 이 모드로 전환하면 Nest.js가 응답 개체를 직접 제어하게 됩니다. 따라서 핸들러 내에서 응답을 직접 관리해야 합니다. 예를 들어, Express.js에서는 res.json() 또는 res.send()와 같은 메소드를 사용하여 응답을 발행해야 합니다. 그렇지 않으면 Nest.js는 응답을 처리하지 않고 HTTP 서버에 빈 응답을 반환합니다.
HTTP
Nest는 모든 표준 HTTP 메서드인 @Get(), @Post(), @Put(), @Delete(), @Patch(), @Options() 및 @Head()에 대한 데코레이터를 제공한다. 또한 @All()은 모든 메서드를 처리하는 끝점을 정의한다.
- GET: 리소스를 가져오기 위해 사용됩니다. 주로 데이터를 요청할 때 사용됩니다.
- POST: 새로운 리소스를 생성하기 위해 사용됩니다. 서버로 데이터를 보낼 때 사용됩니다.
- PUT: 기존 리소스를 업데이트하기 위해 사용됩니다. 전체 엔터티를 업데이트할 때 사용됩니다.
- PATCH: 기존 리소스의 일부를 업데이트하기 위해 사용됩니다. 일부 엔터티의 수정에 사용됩니다.
- DELETE: 리소스를 삭제하기 위해 사용됩니다. 서버에서 데이터를 제거할 때 사용됩니다.
- OPTIONS: 서버가 지원하는 메서드 및 기능에 대한 정보를 요청할 때 사용됩니다.
- HEAD: 서버에게 리소스에 대한 메타데이터를 요청하는 역할, 실제 데이터가 아닌 헤더 정보를 요청할 떄 사용합니다.
Route 와일드 카드
패턴 기반 경로 지원으로 예시는 아래와 같음
- A wildcard in the middle of the route is only supported by express.
@Get('ab*cd')
findAll() {
return 'This route uses a wildcard';
}
상태 코드
- 응답 상태코드 201인 POST 요청을 제외하고 Default 상태 코드는 200이다.
- 핸들러 수준에 데코레이터를 추가하면, 이 동작을 쉽게 변경할 수 있음
- 상태 코드가 정적인 것이 아니라 다양한 요인에 따라 달라지는 경우가 많다.
- 라이브러리별 응답(@Res() 개체를 사용하여 주입)을 사용하거나 오류가 발생한 경우 예외를 던질 수 있습니다.
@Post()
@HttpCode(204)
create() {
return 'This action adds a new cat';
}
헤더
사용자 지정 응답 헤더를 지정하면 @Header() 데코레이터 또는 라이브러리별 응답 개체(및 res.header()를 직접 호출할 수 있다.
@Post()
@Header('Cache-Control', 'none')
create() {
return 'This action adds a new cat';
}
리디렉션
NestJS에서 리디렉션을 수행하기 위해 @Redirect() 데코레이터를 사용할 수 있다.
이를 통해 특정 URL로의 리디렉션을 쉽게 설정할 수 있습니다. 또한, 직접 res.redirect()를 호출하여 리디렉션을 수행할 수도 있다.
@Redirect() 데코레이터는 두 가지 인수를 받는다
- URL: 리디렉션할 대상 URL입니다. 이 인수는 필수입니다
- Status Code: 선택적으로 지정할 수 있는 HTTP 상태 코드입니다. 이 인수를 생략할 경우 기본값으로 302(Found)가 사용됩니다. 리디렉션할 때 사용할 HTTP 상태 코드를 지정할 수 있습니다.
@Controller('example')
export class ExampleController {
@Get()
@Redirect('https://nestjs.com', 301)
@Get('redirect')
@Redirect('https://example.com', 301) // 리디렉션할 URL과 상태 코드 지정
redirectToExample() {
// 리디렉션을 수행할 메서드
}
}
경로 매개변수
동적 데이터를 허용하기 위해 경로 매개변수의 path Param을 추가하여 URL의 해당 위치에서 동적 값을 캡처할 수 있다.
@Get(':id')
findOne(@Param() params: any): string {
console.log(params.id);
return `This action returns a #${params.id} cat`;
}
라우트 경로에 정적 경로와 매개변수가 함께 사용될 때, 정적 경로가 먼저 선언되어야 한다. 그렇지 않으면 매개변수가 있는 경로가 정적 경로를 가로챌 수 있으며, 이로 인해 의도치 않은 동작이 발생할 수 있다.
예를 들어, 다음과 같은 상황:
- /cats/:id 라우트가 있는 경우
- /cats/create 라우트가 있는 경우
// 잘못된 구조의 예시
@Get(':id')
findOne(@Param() params: any): string {
// 처리 로직
}
@Get('create')
create(): string {
// 처리 로직
}
위의 예제에서는 /cats/create와 /cats/:id의 경로가 정의되어 있다.
그러나 만약 매개변수가 있는 경로가 먼저 선언되지 않고, /cats/create 라우트가 먼저 나온다면, /cats/create로의 모든 요청이 findOne() 메소드로 라우팅된다. ( 즉, create도 인수로 받아들여서, fineOne이 수행됨 )
이는 의도치 않은 동작이며, /cats/create로의 요청이 create() 메소드로 라우팅되어야 한다.
따라서 매개변수가 있는 경로는 정적 경로 뒤에 선언되어야 하며, 이를 통해 매개변수화된 경로가 정적 경로를 가로챌 수 없도록 할 수 있다
하위 도메인 (Sub domain) 라우팅
서브 도메인이란 DNS 계층에서 주 도메인을 보조하는 계층이다.
blog.naver.com , mail.naver.com ...
서브 도메인 라우팅을 사용하면 다음과 같은 이점이 있다.
- 조직화: 애플리케이션의 다양한 기능을 서브 도메인별로 분리하여 관리할 수 있다. 예를 들어, 관리자 페이지는 admin.example.com, 사용자 페이지는 user.example.com으로 분리할 수 있다.
- 보안: 각 서브 도메인에 대한 접근 제어를 별도로 설정할 수 있어 보안 관리를 강화할 수 있다.
- 유연성: 서브 도메인별로 다른 서버나 서비스로 라우팅할 수 있어, 시스템 확장성과 유연성을 높일 수 있다.
Controller 데코레이터의 host 옵션을 사용해서 서브 도메인에 대한 요청을 처리하도록 설정이 가능하다.
@Controller({ host: 'admin.example.com' })
export class AdminController {
@Get()
index(): string {
return 'Admin page';
}
}
또한 동적 서브 컨트롤러를 지원하여, 서브 도메인 내의 동적 값을 캡쳐하여 처리하는 것이 가능하다.
@Controller({ host: ':account.example.com' })
export class AccountController {
@Get()
getInfo(@HostParam('account') account: string) {
return account;
}
}
하위 도메인 (Sub domain) 라우팅
스코프란 ? Provider에서는 일반적으로 Nest 프로그램의 수명 주기와 동기화 된 수명(범위)을 갖는다.
Nest 프로그램이 부트 스트랩 될 때 모든 종속성을 해결해야 하기 때문에 모든 프로바이더가 인스턴스화 된다.
마찬가지로 Nest 프로그램이 종료되면 각 프로바이더가 메모리에서 삭제됩니다. 그러나 프로바이더의 수명을 요청 단위로 제한하는 방법도 있습니다. 다만 성능에 문제가 될 수 있기 때문에 특수한 상황이 아니라면 기본 설정된 수명 주기 사용이 권장된다.
NestJs에서는 대부분의 구성요소가 싱글톤으로 작동하는데 이는 Node.js의 멀티 스레드가 아닌 단일 스레드 모델 따르기 때문에 각 요청마다 별도의 스레드를 생성하지 않아도 모든 요청이 처리 될 수 있다. 따라서 대부분의 경우 하나의 인스턴스를 여러 요청에서 공유하는 것이 효율적이다.
- 대부분의 경우 싱글톤 스코프를 사용하는 것이 좋고, 이렇게 되면 인스턴스가 캐시되고 애플리케이션 시작 시 초기화가 이루어짐
- WebSocekt 게이트웨이 같은 경우 요청 스코프를 사용해서는 안된다. 게이트웨이는 실제 소켓을 캡슐화하고 있으므로 여러 번 인스턴스화 될 수 없다. ( 이런 제한은 Passport , Cron 컨트롤러 같은 다른 프로바이더에도 해당 )
하지만 특정 상황에서 각 요청마다 별도의 인스턴스가 필요할 수 있다.이런 경우를 위해 Nestjs는 3가지 종류의 Injection Scope를 지원한다.
Default ( 기본 값 ) | 애플리케이션 전체에서 단 하나의 인스턴스가 공유된다. ( 시작될 때 생성되어 생명 주기에 직접 연결 ) |
REQUEST | 각 요청마다 새로운 인스턴스 생성 모든 요청이 끝나면 생성된 인스턴스는 가비지 컬렉션에 의해 처리됨 |
TRANSIENT | 임시적인 프로바이더로 공유되지 않는다. TRANSIENT 프로바이더를 주입하는 소비자는 새로운 전용 인스턴스를 리턴받음 |
주입 예제
// 특정 요청 스코프 예시
@Injectable({ scope: Scope.REQUEST })
export class CatsService {}
// 커스텀 프로바이더 설정 시 일시적 스코프 예시
{
provide: 'CACHE_MANAGER',
useClass: CacheManager,
scope: Scope.TRANSIENT,
}
비동기성 요청
비동기성 함수는 Promise()를 반환한다. 따라서 NestJs는 자체적으로 지연된 값을 반환할 수 있다.
Nest 경로 핸들러는 RxJS observable streams 을 반환할 수 있고 Nest 아래 소스를 구독하고 스트림이 완료되면 반환한다.
@Get()
async findAll() {
return [];
}
// RxJS observable streams
@Get()
findAll(): Observable<any[]> {
return of([]);
}
페이로드 요청
Post 경로 핸들러는 클라이언트 파라미터 데이터를 @Body() 데코레이터를 추가하여 받아올 수 있다.
타입스크립트를 사용하는 경우 DTO 스키마를 설정해야 한다.
// create-cat.dto.ts
export class CreateCatDto {
name: string;
age: number;
breed: string;
}
// cats.controller.ts
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
ValidationPipe를 사용하면 메서드 핸들러가 수신하지 않아야 할 속성을 필터링할 수 있다. 이 경우 허용 가능한 속성을 화이트리스트에 추가하고, 화이트리스트에 포함되지 않은 모든 속성은 결과 객체에서 자동으로 제거
오류처리
NestJS 컨트롤러 활성화 방법
NestJS에서는 컨트롤러를 사용하여 애플리케이션의 경로를 정의하고, 클라이언트 요청에 대한 처리 로직을 구현한다.
하지만 컨트롤러를 정의하였다고 해서 자동으로 NestJS가 그 존재를 인지하고 인스턴스를 생성하는 것은 아님.
컨트롤러는 항상 모듈에 속해있어야 함
이를 위해 @Module() 데코레이터 내의 controllers 배열에 컨트롤러를 포함시킨다. 아직 다른 모듈을 정의하지 않았다면, 루트 AppModule을 사용하여 CatsController를 포함시킬 수 있다.
@Module() 데코레이터를 사용하여 모듈 클래스에 메타데이터를 추가함으로써, Nest는 이제 어떤 컨트롤러들이 마운트되어야 하는지 파악 가능함
// app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
@Module({
controllers: [CatsController],
})
export class AppModule {}
라이브러리별 특정 응답 객체 사용
NestJS에서 응답을 조작하는 표준 방법 외에, 라이브러리 특정 응답 객체를 사용하여 응답을 조작하는 두 번째 방법이 있다. 이를 위해 @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([]);
}
}
- 이 접근 방식은 작동하며, 실제로 응답 객체(헤더 조작, 라이브러리 특정 기능 등)의 전체 제어를 통해 더 많은 유연성을 제공.
- 그러나 이 방법은 코드가 플랫폼에 종속되게 하고 테스트하기 어렵게 만드는 단점 (응답 객체를 고려).
- 위의 예시에서 볼 수 있듯이, Nest 표준 응답 처리에 의존하는 기능들, 예를 들어 Interceptors 및 @HttpCode(), @Header() 데코레이터와의 호환성을 잃게 됩니다.
- 이를 해결하기 위해 다음과 같이 passthrough 옵션을 true로 설정할 수 있습니다.
@Get()
findAll(@Res({ passthrough: true }) res: Response) {
res.status(HttpStatus.OK);
return [];
}
이제 네이티브 응답 객체와 상호 작용할 수 있으며(예를 들어, 특정 조건에 따라 쿠키나 헤더를 설정), 나머지는 프레임워크에 맡길 수 있습니다.
'FrameWork & Runtime > Node.js (NestJs)' 카테고리의 다른 글
TypeORM (0) | 2022.10.03 |
---|---|
모듈 [ @Module() ] (0) | 2022.07.28 |