DEV Community

Cover image for Nest.js에서 Pipe의 필요성
Ray Kim
Ray Kim

Posted on • Edited on

Nest.js에서 Pipe의 필요성

Nest.js의 Pipe는 굳이 비교하자면 Spring의 Converter 또는 Formatter의 그것과 같다.

하지만 Middlewarehelper가 있는데 왜 Pipe라는 개념까지 도입한 것일까라는 의문이 생긴다.

공식문서의 개념에서 Pipe@injectable decorator을 사용하는 JS class 라고 설명하고 있다. 즉, Provider과 기본 개념이 동일하다.

그렇다면 먼저 injectable이 무엇인지 알아보자.


Injectables

A모 프레임웍에서 에서 파생된 injectable은 간단히 말해 Dependency Injection 패턴을 적용시키겠다는 말이다.

Nest는 Provider 패턴으로 쉽게 dependency를 관리 및 공유할 수 있는데,
한 번의 주입으로 만들어진 의존성 인스턴스를 하위 모듈에서 쉽게 접근할 수 있다는 장점이 있다.

React Context Provider ∝ Nest Module Provider
하위 객체에게 의존성을 주입하는 것은 비슷하다.

이는 인스턴스 한 개만 관리하기 때문에 디버깅을 간단하게 해줄뿐만 아니라 메모리 누수를 막을 수 있다는 매우 큰 이점을 가져다 준다.

다음과 같은 예를 보자.

@Injectable()
export class ExampleService {
  constructor(private memoryLeak = new ArrayBuffer(10000)) {}
}
// ExampleService 인스턴스가 생성될 때마다 10000byte 메모리 누수를 일으킨다.

@Module({
  providers: [ExampleService],
})
export class ExampleModule {}
// 이를 `Injectable`화 하여 주입하면 Nest는 인스턴스를 재사용하게 된다.
Enter fullscreen mode Exit fullscreen mode

이같이 한 번의 Provider 주입만으로 injectable을 하위 모듈간 쉽게 위임하고 재활용할 수 있다.


Dependency Injection
<Dependency Injection>


명시적 injectable을 통한 DI패턴은 유용하지만, 여기까지만 보면 그냥 필요에 따라 Provider에 헬퍼나 팩토리 따위를 담아 사용하는 방법이 떠오른다.

그렇다면 다시 처음으로 돌아가 Pipe는 타 헬퍼과 다른 점이 무엇일까?


Pipe vs. the World

공식문서를 인용하자면 Pipe는 컨트롤러 handler의 매개변수 전처리를 도와주는 특수한 helper라고 정의할 수 있다. 다시 말해 input으로 들어오는 데이터를 지정된 파이프를 거쳐 들어오게끔 하는 것이다.

이는 Middleware의 역할이 아닌가라고 볼 수 있지만 Middleware은 Request에 항상 관여한다면, Pipe는 특정 route handler에 붙어 필요한 데이터값만 선별해 다룬다는 점에 차이가 있다.

이렇게 Pipe를 거친 input을 handler에 전달할 경우 각 단일책임원칙을 위배하지 않음과 동시에 알맞게 정제된 반환값을 받을 수 있다.

또한, Pipe 역시 재사용 가능한 injectable이므로 어느 라우터에 갖다 붙여도 된다는 장점이 있다.


Request flow
<Request flow - Pipe는 모두 재사용 가능하다>


Pipe 또한 당연하게도 exception filter의 혜택을 누릴 수 있다. 특정 매개변수의 에러를 이곳에서 처리하자.


Pipe use-case

Pipe 역할은 이제 알디시피 '맞춤형' 필터라고 볼 수 있으며 평소 middleware이 하는 역할들을 pipe에게도 맡길 수 있을 것이다.

태생이 이렇다 보니, Nest 프레임웍은 자주 쓰이는 기본 Pipe들을 제공한다.

실전으로 들어가 REST API에서 /person/3 endpoint에 요청을 받았다고 생각해보자.

@Controller('person')
export class PersonController {
  @Get(':id')
  async getPerson(@Param('id') id: number) {
    // return a matching person
  }
}
Enter fullscreen mode Exit fullscreen mode

이 때, id가 항상 number로만 들어오면 괜찮지만 만약 3a가 들어올 경우 Pipe를 제공해 변환해주어야 한다.

// ParseIntPipe에 변환 과정을 위임한다.
@Get(':id')
async getPerson(@Param('id', ParseIntPipe) id: number) {
  // return a matching person
}
Enter fullscreen mode Exit fullscreen mode

이처럼 Pipe를 사용해 아주 간단히 getPerson handler의 관심사 분리를 할 수 있다.

Pipe용으로 사용할 수 있는 매개변수는 @Param, @Body, @Query가 있다.

위 예시는 기본적인 Pipe 사용 방법을 제시했지만, 유효성 검증 및 입맛에 따라 커스텀 Pipe도 얼마든지 만들 수 있다.

Top comments (0)