<colbgcolor=#ffffff><colcolor=#E0234E> Nest.js | |
파일:nest_logo.svg | |
개발 | Kamil Mysliwiec |
종류 | 백엔드 웹 프레임워크 |
언어 | TypeScript( JavaScript) |
버전 | v10.4.5 |
라이선스 | MIT 라이선스 |
| |
[clearfix]
1. 개요
Node.js 런타임 위에서 동작하는 TypeScript용 오픈 소스 백엔드 웹 프레임워크.
2. 상세
노드 생태계에서 거의 기본처럼 쓰였던 Express.js에 비해 제어 역전성을 가지는 프레임워크적 성향이 더욱 강하며, NestJS가 상당한 아이디어를 차용한 자바 생태계의 Spring과도 자주 비교된다. 애초에 Angular로부터 영감을 받은 프로젝트라 구조적으로 유사성이 많이 보인다.다만 NestJS 자체는 독립적인 프레임워크가 아니며, 내부적으로 기존의 웹 프레임워크들의 기능을 일관된 API로 노출해 주는 거대한 어댑터(Adapter)에 가깝다. 현재까지는 공식적으로 Express.js 및 Fastify를 지원한다.
3. 특징
3.1. 구조화된 아키텍처
NestJS는 코드를 거시적인 관점에서 Provider, Controller, Module로 분리하며 이런 작은 컴포넌트들끼리의 조합(DI)을 통해 전체 애플리케이션을 완성한다.3.1.1. Provider
#!syntax typescript
@Injectable()
export class Service {}
nest의 가장 간단하고 작은 단위의 컴포넌트를 표현한다.
기본적으로
@Injectable()
데코레이터로 나타내며 이름에서도 알 수 있듯이 프로바이더는 다른 프로바이더나 컨트롤러에 주입(Inject)될 수 있다.Provider의 종류로는 가장 간단한 Service부터 시작해서 Middleware, Pipe, Guard, Interceptor 등이 존재한다.
3.1.2. Controller
provider들이 비즈니스 로직을 추상화한다면, Controller는 실제 외부와의 통신과 라우팅 등을 담당한다.주로 쓰이는 것은 당연히 HTTP지만 추가적인 모듈을 통해 게이트웨이( 웹소켓), TCP, 이벤트 컨슈머(Kafka), gRPC 등의 계층도 컨트롤러에서 사용할 수 있다.[1]
3.1.3. Module
#!syntax typescript
@Module({
imports: [],
controllers: [],
providers: [],
exports: []
})
class Module {}
수평적으로 흩어진 Provider와 Controller들을 논리적인 기능이나 도메인에 따라 하나로 묶어주는 역할을 하며, 재사용성을 높여준다.
예를 들어 고객의 주문을 받는
BucketController
, 주문을 외부 K-V 스토어에 캐싱하는 CacheService
, 고객의 신용카드 정보를 확인하는 CreditService
, order
라는 토픽으로 이벤트를 발행하는 프로듀서 ProduceOrder
컨트롤러가 존재할 때, 이러한 여러 컴포넌트를 하나의 기능으로 묶어 OrderModule
로 분리할 수 있다.단순히 코드를 부분이나 폴더로 묶어주는 것 뿐만 아니라 계속해서 재사용될 기능을 정의할 수도 있다. 가령 레디스 서버에 요청을 주고받는 서비스를 앱의 다른 부분에서도 사용해야 한다면,
RedisModule
등으로 이를 모듈화한 후 exports
에 노출할 provider를 나열하면 다른 모듈에서 그 서비스를 종속성으로 주입할 수 있다.Nest개발시 흔히 자주 쓰이는 모듈로는
ConfigModule
, HttpModule
등이 존재한다.[2]만약 모듈이 너무 크거나 초기화에 오래 걸린다면,
LazyModuleLoader
클래스를 주입해 해당 모듈을 lazy-loading하도록 설정할 수도 있다. 보통 3.2. 데코레이터
데코레이터는 여러 프로그래밍 언어에서 사용되는 개념이지만, 자바스크립트 생태계에서는 아직 조금 낯선 개념일 수 있다. NestJS 에서는 데코레이터 개념을 채용해서 사용하고 있다. HTTP route를 위한 데코레이터, 요청의 param에 대한 데코레이터 등 미리 만들어둔 데코레이터들이 있으며, 사용자가 정의해서 추가할 수 있다.3.3. Express와의 차이점
기존의 노드 개발 환경에서 사실상 유일했었고 현재도 사실상 업계 표준으로 인정받는, 그야말로 자바스크립트 생태계에 막대한 영향력을 끼치는 프레임워크 중 하나다 보니 두 프레임워크가 비교되는 일이 굉장히 잦다.express는 간단한
req-res
미들웨어 콜백 스타일과 당시 주류였던 commonjs 특유의 제약 없는 모듈 시스템이 합쳐져 굉장히 프로젝트 구조가 자유로운 프레임워크가 되었고, 비즈니스 로직이 복잡하지 않을 때는 거의 한 파일에 코드를 다 집어넣어도 돌아가는[3] 간결성과 유연성을 가지게 되었다.다만 서버사이드 자바스크립트 생태계가 점점 발전하고 요구사항이 커지면서 명확한 구조도, 코딩 스타일도 없는 express로는 협업 등에 문제가 생겼고 이걸 해결하기 위해 express MVC등 여러 패턴들이 등장했지만 그중 누구도 nest만큼 성공적이지 못했다.
재미있는 점은 nest가 모든 걸 처음부터 하나하나 개발하지 않고 기존 express 생태계의 기술들을 가져다 사용했다는 부분인데,
typeORM
, ClassValidator
, cookie-parser
, axios
, multer
, ClassTransformer
, passport
등 기존의 익스프레스 개발자라면 반갑게 느껴질 만한 패키지들을 그대로 사용하거나, nest에 어울리게 래퍼로 감싸서 제공하는 것을 볼 수 있다. 게다가 사실 내부적으로는 express를 사용하기 때문에 결국 req
, res
등의 객체들을 사용할 수도 있다.이러한 express 친화적인 부분들이 기존의 개발자들에겐 다른 프레임워크에 비해 훨씬 커다란 장점으로 다가오고, 기존 프로젝트의 마이그레이션 역시 쉽게 진행될 수 있도록 뒷받침해준다는 사실 또한 큰 특징이라 볼 수 있다.[4]
Express, Nest를 Python 백엔드 프레임워크에 비유하자면 Express는 Flask고, Nest.js는 Django에 비유할 수 있다.
3.4. TypeScript Native
기존의 자바스크립트 프레임워크와는 다르게, '타입스크립트를 지원할 수 있다'가 아니라 처음부터 '타입스크립트로 쓸 것을 가정하고' 만들어졌다.[5] 물론 쓸려면 js로도 쓸 수는 있으며 자세한 것은 후술.타입스크립트를 단순한 기초 이상으로 활용하며 이 프레임워크의 핵심 컨셉 자체에 각종 타입스크립트 특수적인 개념들이 들어가기 때문에 타입스크립트를 모른다면 러닝커브가 상당히 높을 수 있다. 반대로 앵귤러 개발자들이었다면 provider, 데코레이터, Compodoc, DI 패턴 등 친숙한 개념들이 많아 가장 배우기 쉽다.
가장 눈에 띄는 특징으로, 앵귤러만큼 데코레이터를 도처에 활용한다. 그 외에도 표준화된 인터페이스의 사용(
OnModuleInit
등), 생성자 DI 등을 찾아볼 수 있다.타입스크립트의 타입을 활용한 DTO를 사용한다. 즉 다음과 같이 선언한 클래스는
#!syntax typescript
class Dto {
constructor(
private readonly name: string,
private readonly age: number,
private readonly isMan: boolean,
) {}
}
어떠한 별도의 데코레이터 등이 없이도 자동으로 클라이언트에 전달되는 시점에서#!syntax typescript
@Controller()
class SomeController {
@Get()
returnDto(): Dto {
return new Dto('namuwiki', 123, false)
}
}
아래와 같이 자동으로 직렬화된다.#!syntax json
{
"name": "namuwiki",
"age": 123,
"isMan": false
}
또한
classTransformer
나 classValidator
등을 이용해서 추가적인 비즈니스 로직을 작성할 수도 있다.주의할 점으로, DTO 클래스에 부분적인 변경을 가하기 위해서는 타입스크립트의 내장
Partial<T>
, Omit<T, K extends keyof T>
등을 사용하기보단 @nestjs/mapped-types
를 사용하는 것이 좋은데, 이는 내장 타입은 단지 mapped-type이기 때문에 데코레이터가 붙은 클래스에 사용하면 런타임에서 읽어봐야 할 정보가 날아가기 때문이다.사실상 100% 타입스크립트로 쓰일 것을 가정하고 만들어진 프레임워크지만, 자바스크립트로도 작성이 불가능한 것은 아니다. 다만 100% 네이티브로 돌아가는 것은 아니고 자바스크립트 스캐폴딩 기준으로는 바벨을 사용해 데코레이터를 컴파일한다. 다만 이 과정이 느리기 때문에 SWC를 사용할 수도 있다(이는 타입스크립트 코드베이스에서도 마찬가지). swc용 스타터 템플릿을 보면 성능 차이가 얼마나 나는지 실감할 수 있다.
3.5. 강력한 CLI
#상당히 압도적인 특징 중 하나인데, 기존 스프링 등과 같이 파일 구조가 복잡하고 동시에 여러 부분을 변경할 필요가 있는 프레임워크는 매번 빈번히 여러 파일을 수정하기도 불편하고 실수가 발생하기 쉬웠기에 IDE 레벨에서 여러 작업의 자동화를 지원해왔다.
다만 자바스크립트의 경우는 일관된 구조도 없거니와 통합적인 개발 환경이 통일되지 않고 무엇보다 매번 같은 작업을 반복해야 한다면 해당 프레임워크의 만족도가 떨어질 수 있는데, nest의 경우 강력한 cli를 만들어 이를 해결했다.
예를 들어
#!syntax typescript
$ nest g res users
를 입력하면 CRUD 컨트롤러, 서비스, 유닛 테스트, DTO, 심지어 엔티티 정의까지 자동으로 만들어 준다. 따라서 cli를 잘 활용할수록 nest 개발중 기능 추가 등이 상당히 편해지는 것을 느낄 수 있다.또한 다양한 cli플러그인을 사용해 기능을 확장할 수 있다. 대표적인 예시가
@nestjs/swagger
.[6]4. 사용해 보기
4.1. 프로젝트 생성
#!syntax sh
$ npm i -g @nestjs/cli
$ nest new project-name
# 또는
$ npx -y @nestjs/cli new project-name
project-name
라는 이름으로 가장 기본적인 보일러플레이트 프로젝트를 생성해 준다.만약 자바스크립트로 프로젝트를 생성하고 싶다면
-l javascript
옵션을 줄 수 있다.5. NestJS를 사용하는 기업
6. 관련 문서
[1]
몇몇 경우는
Transporter
이기 때문에 앱 초기화 전 별도로 마이크로서비스로 선언해야 한다.
[2]
간혹 ConfigModule
로 다른 모듈을 설정해야 하는 경우도 종종 존재하는데, 이럴때는 다른 모듈의 초기화를 별도의 콜백 안에서 수행하기 위해 Dynamic module
기능을 사용해야 한다.
[3]
이런 장점(?) 덕분에 node런타임을 지원하는 대부분의 람다 서비스에서는 전용 SDK를 제공하더라도 express랑 비슷한 형태로 제공해준다. 그러면 정말이지 한 파일에 람다 코드를 다 작성하게 되는 일이 빈번하다.
[4]
다만 Spring 등의 프레임워크에 비해 상대적으로 그렇다는 말이지 fastify나 koa등의 express식 스타일을 거의 비슷하게 따라가는 다른 프레임워크들에 비해선 러닝커브가 압도적으로 높은 것이 사실이다.
[5]
이는
Angular와도 상당히 비슷한 특징이다.
[6]
타입스크립트의 메타데이터만으로는 자동으로 OAS문서를 생성하기에 충분한 정보를 찾을 수 없기 때문에 컴파일 전 별도의 파서를 실행해 필요한 타입 정보를 얻어내기 위해서 cli 플러그인이 필요하다.
[7]
NestJS 프로젝트에 지속적인 기여와 재정적인 후원을 하고 있다.
#