✅ Nestjs
💡 **Nest.js는 기존 Node.js를 확장하여 Typescript를 기반으로 API 제작과정에서 OOP, DI, Singleton 등을 적용한 프레임워크입니다.
→ 개발자가 인스턴스를 관리하지 않고 NEST.JS 직접 모듈에 주입을 하면서 인스턴스를 생성하고 주입을 해줍니다.**
😁 Provider
프로바이더의 역할
- provider는 의존성주입을 하는 DI 컨테이너와 같은 역할을 한다.
- 이러한 provider, IoC 컨테이너 들이 프레임워크에서 동작하며 이러한 것들을 기준으로 프레임워크를 선택해야 한다.
- service, repository, factory등을 넣을 수 있다.
😁 DI
우리는 DI가 왜 필요한가?
- 의존관계 주입은 정확하게는 외부에서 의존관계를 넣어준다. 라는 의미를 가진다.
즉 실행시점에서 추상화 된 객체를 만들어 사용할 곳에 뿌려준다는 소리다.
- 이렇게 하는 이유는 클래스를 직접 참조한다면 결합도가 올라가 객체에 영향을 준다.
- 메모리 측면에서도 객체를 필요한 곳에서 모두 생성하는 것이 아니기 때문에 효율적이다.
DI를 하게 하려면?
@Injectable()
데코레이터를 사용해야 한다.
- 해당 클래스를 의존성 주입을 통해 사용할 수 있도록 한다.
- 해당 클래스가 의존성 주입을 받을 수 있게 한다.
가드, 인터셉터 같은것들은 왜 provider에 등록안할까?
- HTTP요청은 각각 독립적인 컨텍스트를 갖고 있기 때문에 인스턴스를 공유하는 것은 문제가 된다.
😁 Nest.js 의 IoC 컨테이너
IoC 컨테이너란?
- IoC Container는 Provider를 등록하고 관리하는 객체이다.
Provider는 NestJS의 라이프 사이클과 동기화된 Scope를 가지며 프로그램이 시작될 때 모든 종속성을 처리한다.
Provider는 의존성 주입을 통해 다른 클래스와 관계를 맺을 수 있는데,
IoC Container는 Provider의 메타데이터를 분석하여 의존성 그래프를 생성한다.
IoC Container는 의존성 그래프에 따라 필요한 Provider를 인스턴스화하고 주입하며,
이 과정에서 @Injectable 데코레이터가 사용된다.
또한 인스턴스화된 Provider를 저장하고, 참조할 수 있게 해주는데 이때는 @Inject 데코레이터가 사용된다.
컨테이너가 하는 일
- 객체의 생성:
@Injectable()
데코레이터가 붙은 클래스의 인스턴스를 생성합니다. 이때, 클래스의 생성자에 @Inject()
데코레이터가 붙은 매개변수가 있다면, 해당 매개변수에 해당하는 토큰의 인스턴스를 주입합니다.
- 객체의 재사용: 한 번 생성된 인스턴스는 컨테이너에 캐싱되어 재사용됩니다. 이를 통해 메모리 사용량을 줄이고, 인스턴스 간 상태 공유를 관리할 수 있습니다.
- 객체의 생명주기 관리: 컨테이너는 인스턴스의 생명주기를 관리하며, 필요에 따라 인스턴스를 제거합니다.
😁 모듈 결합 방법
- A 모듈 B 모듈이 있다 → A 모듈의 A 서비스를 B 모듈에 주입하고 싶다. → A 모듈에서 A 서비스를 exports 한다. → B 모듈에서 A 모듈을 imports 한다. → B 모듈에서 A 서비스를 provider에 등록한다.
- 생성된 A 서비스를 사용하면 된다. 만약 provider에 서비스를 따로 등록하게 되면 해당 서비스 객체가 생성되어 다른 클래스들도 요구한다. 싱글톤패턴을 위반한다.
- exports를 안해도 동작이 되던데 exports를 해주는 이유가 무엇인가? 의 답이 된다.
✅HTTP 요청 순환 주기
✔️ Middleware
- 라우트 핸들러 이전의 req,res 객체에 접근할 수 있다. 실행을 종료하지 않으면 next() 함수를 무조건 호출해야 한다.
- 클래스 혹은 함수로 작성할 수 있다.
- 클래스라면 DI 기능을 활용하여 다른 서비스에게 의존하지 않게 된다. 이 말은 다른 서비스들을 이용하는데 문제가 존재하지 않는다.
- 테스트가 용이하다는 장점이 있다.
- 재사용과 확장성의 장점이 있다.
- 인터셉터와 크게 다른점은 라우트핸들러 전에만 존재할 수 있다는 점이다.
- 미들웨어는 express 스타일로 적용하므로 라우트주소설정에 대해서 유연하다. Guard와 Interceptor는 데코레이터 형식이라 그 반대다.
✔️ Interceptor
의미
- NestJS 내에서 인터셉터란 컨트롤러 전, 후에서 다양한 역할을 해주는 생명주기를 의미한다
- 실행컨텍스트와 콜핸들러에 접근이 가능하다. 즉 라우터 핸들러 전과 후 객체에 접근이 가능하다.
- RxJS Observable 객체를 리턴값으로 가지고 있어야 한다.
- 이유는 node.js는 비동기 아키텍쳐이기때문에 반환값을 비동기 데이터스트림으로써 처리해야하기 때문이다.
- Reactive Extensions for Javascript 는 비동기 이벤트나 시간을 배열처럼 처리할 수 있는 라이브러리다.
- 비동기 로직을 Data로써 처리가 가능하다
- 모든 코딩을 스트림과 함수형으로 쉽게 작성할 수 있다.
- rxjs 는 비동기 객체에 대해서 직관성을 부여한다. pull 메커니즘이 아닌 push 메커니즘을 사용한다.
범위
- 모듈 밖에서 작동하는 전역 인터셉터
- 모듈 내에서 작동하는 전역 인터셉터
- 모듈 내 특정 컨트롤러 혹은 메서드에 작동하는 인터셉터
용도
- 핸들러 처리 시간 로그 출력
- 반환 형태 템플릿 적용
- 예외 처리 (=FIlter)
- 요청 시간 제한 인터셉터
- 캐싱 인터셉터
코드
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { Observable } from "rxjs";
@Injectable()
export class TestInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> {
return next.handle().pipe(
)
}
}
Reference
인터셉터 레퍼런스
Rxjs 레퍼런스
✔️ Guard
- Nestjs에서 인가를 담당하는
@injectable
과 annotated 된 클래스.
- canActive 함수를 반드시 포함해야하며 canActive 함수에는 실행컨텍스트가 인자로 들어있다.
- 실행컨텍스트에는 현재 처리되는 요청,응답 객체, 라우트 핸들러, 클래스와 메서드 정보, 인젝트 상태 등을 포함한다.
- 실행컨텍스트에 접근할 수 있기 때문에 미들웨어보다 더 디테일하게 인가를 해줄 수 있다.
- ExecutionContext
- 이 객체는 현재 실행 중인 처리 핸들러에 대한 특정 메타데이터를 저장합니다.
- 애초에 ArgumentsHost 에게 상속받고 있다. 즉 ArgumentsHost 의 기능을 사용가능하다.
- ArgumentsHost
- 이 객체는 현재 실행 중인 함수의 인자 목록을 포함합니다.
기본적으로 ArgumentsHost는 Node.js의 기본 함수와 함께 사용될 수 있지만, NestJS에서는 ExecutionContext를 상속하여 더 많은 기능을 제공합니다.
ArgumentsHost를 사용하여 현재 처리 중인 요청의 인자를 가져오거나 조작할 수 있습니다.
Reference
https://velog.io/@junguksim/NestJS-%EB%85%B8%ED%8A%B8-2-Guards)
→ Guard + SetMetadata 이용하
✔️ Filter
- NestJS에서 예외 필터란 비즈니스 로직 레벨에서 처리되지 않은 모든 예외를 사용자 친화적인 방식으로 처리하는 역할을 한다.
따라서 예외 필터를 이용하면 프로그래머가 예상치 못한 에러가 발생하더라도 서버가 죽지 않고, 또한 로깅 등의 작업을 함께 수행할 수 있다.
기본내장필터
- BadRequestException
- UnauthorizedException
- NotFoundException
- ForbiddenException
- NotAcceptableException
- RequestTimeoutException
필터 적용 레벨
- 모듈 밖에 위치하는 전역 필터
- 모듈 내에 위치하는 전역 필터
-
예시코드
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
export class AppModule {}
- 컨트롤러 레벨에 작동하는 필터
-
예시코드
@UseFilters(HttpExceptionFilter)
export class CatsController {}
- 메서드(핸들러) 레벨에 작동하는 필터
Reference
https://one-armed-boy.tistory.com/entry/NestJS-Request-Lifecycle-5-Exception-Filter
✔️ Pipe
NestJS에서 파이프란 컨트롤러의 앞단에서 사용자의 요청에 대해 다음의 역할을 수행하는 생명주기를 의미한다.
- 변환: 사용자의 요청 속 데이터를 원하는 형태로 변경 후 컨트롤러에 넘겨준다.
- 검증: 사용자의 요청 속 데이터에 대해, 컨트롤러 단에 도달하기 전 검증을 수행하고 검증이 실패할 시 이를 컨트롤러 단에 넘기지 않고 바로 사용자에게 에러를 반환한다.
파이프의 존재 이유?
- NestJS의 아키텍쳐에서 파이프가 따로 존재하는 이유는 바로 관심사 문제를 해결하기 위함이다.
파이프에서 수행하는 로직은 컨트롤러 내에 위치해도 되지만,
그럴 경우 컨트롤러의 관심사가 모호해질 수 있으며 비슷한 데이터의 검증에 대해 재사용성도 나빠진다.
따라서 NestJS에서는 메인 비즈니스 로직의 오염을 방지하고자 데이터 검증 관련 로직을 파이프에 분리하여 작성한다.
내장 파이프클래스
- ValidationPipe
- ParseIntPipe
- ParseFloatPipe
- ParseBoolPipe
- ParseArrayPipe
- ParseUUIDPipe
- ParseEnumPipe
- DefaultValuePipe
- ParseFilePipe
→ 이를 활용해 기본적인 검증, 변환 파이프를 사용할 수 있다.
커스텀 파이프클래스
- 예시코드
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
@Injectable()
export class ValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
return value;
}
}
- 위 예시는 공식문서의 모든 값을 통과시키는 파이프 예시이다. transform 메서드는 2개의 인자를 갖는다.
첫번째 인자인 value는 컨트롤러에서 처리되는 인자이고(다만 컨트롤러에 도달하지는 못한 상태),
두번째 인자인 metadata는 앞선 value의 메타데이터이다.
파이프 적용
- 모듈 밖에 위치하는 전역 파이프
- 모듈밖에 위치하기 때문에 다른 모듈간의 의존성 관계를 맺을 수 없다.
- 모듈 내에 위치하는 전역 파이프
- 의존성을 주입하여 사용. 공식문서에서 추천하는 방법
- 컨트롤러 메서드(핸들러) 레벨에 작용하는 파이
- 메서드 파라미터(인자) 레벨에 작용하는 파이프
Reference
https://one-armed-boy.tistory.com/entry/NestJS-Request-Lifecycle-4-Pipe