아래 글은 https://docs.nestjs.com/ 의 내용을 번역한 것 입니다.
수정사항은 언제든지 댓글로 남겨주세요.
NestJS | A progressive Node.js framework
인트로덕션
Nest는 효율적이고, 규모를 조절할 수 있는(scalable) Node.js의 서버사이드 어플리케이션을 구축하기 위한 프레임워크입니다.
progressive 자바스크립트를 쓰며, TypeScript 또한 완벽하게 구현해낼 수 있습니다. (pure JavaScript 또한 가능합니다.)
그리고 OOP(Object Oriented Programming), FP(Functional Programming), FRP(Functional Reactive Programming)의 요소들 또한 포함합니다.
NestJS는 Node의 가장 핵심적인 문제였던 - 아키텍처 Architecture - 의 부재를 해결하기 위해 만들어졌습니다.
인스톨
Nest CLI로 쉽게 Nest 프로젝트를 시작할 수 있습니다.
$ npm i -g @nestjs/cli
$ nest new project-name
프로젝트 시작하기
1. 프로젝트를 시작할 폴더 생성
2. 폴더안에서 nest 기본 구조 생성 `npm i -g @nestjs/cli` `nest new ./`
3. 앱 실행 `npm start:dev`
Controllers
컨트롤러는 클라이언트로부터 들어오는 requests와 반환하는 responses를 핸들링합니다.
컨트롤러의 목적은 애플리케이션 안에서 각각의 특정한 요청들을 처리하는데 있습니다. 컨트롤러의 라우팅 메커니즘이 어떤 컨트롤러가 어떤 요청을 받아야 하는지 결정합니다. 대부분 하나의 컨트롤러에는 여러개의 경로를 가지고 있으며 각각의 다른 경로는 다른 액션을 보여주게 됩니다.
가장 기본적인 컨트롤러를 만들기 위해서는 클래스와 데코레이터를 사용합니다. 데코레이터는 클래스와 그에 맞게 필요한 메타데이터를 연계시켜주고 Nest에 라우팅 맵을 만들수있게 도와줍니다.
힌트
빠르게 CRUD 컨트롤러를 만들기 위해서, CLI의 CRUD generator를 쓸 수 있습니다. : `nest g resource [name]`
Routing
다음 예제에서는 @Controller() 데코레이터를 써주면서 `cats`라는 부가적인 라우트 경로의 접두부를 부여합니다. 접두부를 부여해줄 경우에는 연관된 경로들이 쉽게 그룹을 지을 수 있게 도와주기 때문에 반복적인 코드사용을 줄여주게 됩니다.
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats''
}
}
Providers
Providers는 Nest의 핵심적인 개념입니다. 대부분의 기본 Nest 클래스는 서비스, 리포지토리, 팩토리, 헬퍼 등 프로바이더로 취급될 수 있습니다. Provider의 중요한 점은 dependency로 주입될 수 있다는 것인데, 이것은 객체들이 서로에 대해 다양한 관계를 맺을 수 있게하고, 이러한 객체들의 인스턴스의 연결은 Nest 런타임 시스템의 대표되는 기능입니다.
이전에 만들어둔 CatsController에서 Controllers가 HTTP의 요청을 다뤄주고 providers에게 조금 더 복잡한 과제들을 위임합니다.
Provider 등록하기
Provider를 사용하기 위해서는 이것을 Nest에 등록해주어야 합니다. 등록을 위해서 module 파일에 providers 항목안 해당 모듈에서 사용하고자 하는 Provider를 넣어주면 됩니다.
Services
서비스는 소프트웨어 개발 내 공통 개념이며, Nest와 JavaScript에서만 쓰이는 개념이 아닙니다. @Injectable 데코레이터로 감싸져 모듈에 제공되며, 이 서비스 인스턴스는 어플리케이션 전체에서 사용될 수 있습니다. 서비스는 컨트롤러에서 데이터의 유효성 체크를 하거나 데이터베이스에서 아이템을 생성하는 등의 작업을 하는 부분을 처리합니다.
간단한 CatsService를 만들어봅시다. 이 서비스는 provider로서 데이터 저장과 회수를 처리하는데 CatsController와 함께 쓰이도록 고안되었습니다.
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
}
우리의 CatsService는 하나의 프로퍼티와 두개의 메소드를 가진 간단한 클래스입니다. 여기서 새로운 특징은 @Injectable() 이라는 데코레이터를 썼다는 점입니다. Injectable() 데코레이터는 메타데이터 CatsService가 Nest IoC 컨테이너에 의해 작동된다는 메타데이터를 포함합니다. 또한 이 예제에서는 Cat이라는 인터페이스를 쓰고 있습니다. 이 인터페이스는 아래와 같습니다.
export interface Cat {
name: string;
age: number;
breed: string;
}
이제 cats를 가져올 수 있는 서비스가 만들어졌으니, CatsController 안에서 사용해줍니다. constructor(private userService: UserService) {} 는 클래스의 생성자로써 userService의 타입만 지정합니다. userService는 create라는 함수를 호출합니다.
의존성 주입은 간단히 Nest의 틀에 맞춰 어떠한 값을 정해주면 Nest가 알아서 컨트롤을 해주는 기능입니다.
CatsService 부분에서 Injectable 데코레이터를 통해 CatsService를 Injectable한 provider로 지정했기 때문에 CatsController 클래스 내의 constructor에서 단순히 catsService의 타입을 catsService로 지정함으로써 Nest가 catsService의 생성과 소멸을 관리합니다.
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
@Controller('cats')
export class CatsController {
constructor(private CatsService: CatsService) {}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
CatsService는 class constructor를 통해 주입되어 집니다.
Dependency injection
Nest는 의존성 주입(Dependency injection)이라는 강한 디자인 패턴을 바탕으로 지어졌습니다. 다행히 Nest에서는 타입스크립트의 역량 덕분에 디펜던시를 처리하기가 쉬운데 타입만으로도 해결이 가능하기 때문입니다. Nest에서 Dependency Injection은 클래스의 Constructor안에서 이루어 집니다.
의존성 주입이 필요한 이유
한 클래스안에서 함수가 호출되기 위해 다른 클래스가 필요로 하는 경우, 의존성을 갖게 된다고 말합니다. 이와 같이 코드를 설계했을때, 결합도(Coupling)가 높아지게 되고, 코드의 재활용성이 떨어집니다. 그렇기 때문에 의존성 주입으로 결합도를 낮춘 유연한 코드를 작성해 코드의 재활용성을 높여줍니다. 그러므로 의존하는 클래스를 직접 생성이 아닌 주입시켜줌으로써 한 클래스를 수정하였을 때, 다른 클래스들도 수정해야하는 상황을 막을 수 있습니다.
Modules
모듈은 @Module() 데코레이터로 주석이 달린 클래스입니다. @Module() 데코레이터는 Nest가 애플리케이션의 구조를 구성하는데 사용하는 메타데이터를 제공합니다. 각 애플리케이션에는 하나 이상의 모듈(루트 모듈)이 있고, 이 루트 모듈은 Nest가 사용되는 시작점이 됩니다. Nest는 모듈을 이용해 provider과의 관계와 종속성을 풀어나갑니다.
App Module이라는 루트 모듈이 있다면 그 안에 BoardModule과 AuthModule을 생성할 수 있습니다. 그리고 각 모듈안에는 Controller, Entity, Service 등이 있습니다.
Module 생성하기
모듈 생성 명령어는 `nest g module 새로운 모듈이름` 입니다.
Middleware
미들웨어는 라우트 핸들러의 전에 호출되는 기능입니다. 미들웨어는 객체의 request와 response에 접근할 수 있고, `next()` 미들웨어는 애플리케이션의 request-response 사이클 안에서 작동합니다.
네스트 미들웨어는 기본적으로 express의 미들웨어와 같습니다. 아래의 설명은 express의 도큐먼트에서 다룬 미들웨어의 성능입니다.
미들웨어는 다음의 과제들을 수행할 수 있습니다.
· 어떤 코드도 작동시킬 수 있습니다.
· 객체의 request와 response에 변화를 줄 수 있습니다.
· request-response의 사이클을 멈출 수 있습니다.
· 스택에서 다음의 미들웨어 기능을 호출합니다.
· 현재 미들웨어가 request-response 사이클을 끝내지 않는다면, nest()를 불러와 다음 미들웨어에게 권한을 넘겨주어야 합니다.
아래 예제는 커스텀한 네스트 미들웨어를 function 또는 클래스에 @Injectable()로 주입시켜줍니다.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
next();
}
}
Nest에는 여러가지 미들웨어가 있습니다. Pipes, Filters, Guards, Interceptors 등의 미들웨어로 취급되는 것들이 있는데 각각 다른 목적을 가지고 사용되고 있습니다.
Exception filters
Nest는 어플리케이션 안에서 처리되지 않은 예외들을 위해 미리 지어진 exceptions layer를 가지고 있습니다. 이 레이어에서는 어플리케이션 코드에서 예외가 처리되지 않았을때를 포착하여 자동적으로 유저 친화적인 응답을 보내도록 되어있습니다.
예를 들어, CatsController에서 우리는 findAll() 메소드를 가지고 예외를 준다고 가정을 해봅니다.
@Get()
async findAll() {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
그리고 클라이언트가 이 엔드포인트를 불러낸다면, 응답은 다음과 같아집니다.
{
"statusCode" : 403,
"message" : "Forbidden"
}
Pipes
파이프는 @Injectable() 데코레이터로 주석이 달린 클래스입니다. 파이프는 data transformation과 data validation을 위해 사용됩니다. 파이프는 컨트롤러 경로처리에 의해 처리되는 인수에 대하여 동작합니다.
- Data Transformation: 입력 데이터를 원하는 형식으로 변환. 만약 숫자를 받기 원하는데 문자열 형식으로 온다면 파이프는 자동으로 숫자로 바꿔줍니다.
- Data Validation: 입력 데이터를 평가하고 유효한 경우에 변경되지 않은 상태로 전달합니다. 그렇지 않다면 데이터가 올바르지 않을 때 예외를 발생시킵니다.
Pipe를 사용하는 법(Binding pipes)
파이프를 사용하는 방법은 세가지로 나눠집니다. Handler-level Pipes, Parameter-level Pipes, Global-level Pipes입니다.
- Handler-level Pipes: 핸들러 레벨에서 @UsePipes() 데코레이터를 이용해서 사용할 수 있습니다. 이 파이프는 모든 파라미터에서 적용됩니다.
- Parameter-level Pipes: 파라미터 레벨의 파이프이기에 특정한 파라미터에게만 적용이 되는 파이프입니다.
- Global Pipes: 글로벌 파이프로 애플리케이션 레벨의 파이프입니다. 클라이언트에서 들어오는 모든 요청에 적용이 됩니다. 가장 상단 영역인 main.ts에 넣어주면 됩니다.
커스텀 파이프를 이용한 유효성 체크
먼저 PipeTransform이란 인터페이스를 새롭게 만들 커스텀 파이프를 구현해주어야 합니다. 이 PipeTransform 인터페이스는 모든 파이프에서 구현해주어야 하는 인터페이스입니다. 그리고 이와 함께 모든 파이프에서는 transform() 메소드가 필요합니다. 이 메소드는 Nest에서 인자(arguments)들을 처리하기 위해 사용합니다.
'Backend' 카테고리의 다른 글
[스프링] 회원가입 암호화기능 분석 (0) | 2022.01.16 |
---|