[NestJS] 커스텀 데코레이터

nagosu·2024년 4월 9일
1
post-thumbnail

서론

우선 시작하기 전에 NestJS를 사용해본 많은 개발자들이 컨트롤러 내에서 데코레이터를 활용한 경험이 있거나 본 적이 있을 것이다.

예를 들어, @Post()와 같은 방식으로 데코레이터를 사용해 특정 작업을 수행하는 방식이 있다.

@Post()

이외에도 @Body(), @Query(), @Param() 과 같이 매개 변수에 특정 데코레이터를 적용하는 방법도 있다.

@Body()
@Query()
@Param()

여기에는 @Req()@Res() 같은 데코레이터도 포함이 되어 있는데 가능하면 이 둘의 사용을 지양하는 것이 좋다고 권장된다.

예시로, 다음과 같이 @Req()를 사용한 코드가 있다.

@ApiOperation({ summary: '내 정보 조회' })
@Get()
getUsers(@Req() req) {
	return req.user;
}

이 방식은 특정 플랫폼(Express)에 종속되어 있고 추후 변경하기도 어렵고 테스트하기도 까다롭다는 단점이 있다.

NestJS에서는 특히 JWT 토큰res.locals.jwt에 저장하는 경우가 많은데 이는 Express에서 미들웨어 간 변수를 공유하는 데 사용된다.

이러한 문제를 해결할 수 있는 방법이 커스텀 데코레이터이다.

커스텀 데코레이터를 통해 req.userres.locals.jwt와 같은 부분을 @Req(), @Res()를 사용하지 않고도 처리할 수 있으며 이를 통해 req.user타입이 추론이 되지 않는 문제도 해결할 수 있다.

커스텀 데코레이터 만들기

몇 가지 예제들을 통해서 커스텀 데코레이터를 만드는 방법을 보자.

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator(
	(data: unknown, ctx: ExecutionContext) => {
		const request = ctx.switchToHttp().getRequest();
		return request.user;
	}
)

여기서 createParamDecorator를 사용하여 커스텀 데코레이터를 사용한다.

이 함수는 익명 함수를 매개 변수로 받으며 data와 ExecutaionContext 타입의 ctx를 매개 변수로 한다.

참고로, 여기서의 ExecutionContext(실행 컨텍스트)는 Javascript에서의 실행 컨텍스트와는 개념이 다르다.

이를 통해 HTTP 요청을 가져오고, request.user를 반환하는 형태로 데코레이터가 작성되어 있다.

위와 같이 작성하면 우리는 User라는 데코레이터를 직접 만들어준 셈이다.

이렇게 정의된 User 데코레이터는 다음과 같이 사용할 수 있다.

기존의 @Req() 부분을 @User()로 변경해 사용한다.

@ApiOperation({ summary: '내 정보 조회' })
@Get()
getUsers(@Req() req) {
	return req.user;
}

---------------------------------------------------------------

@ApiOperation({ summary: '내 정보 조회' })
@Get()
getUsers(@User() user) {
	return user;
}

이 변경을 통해서 user 변수가 req.user의 값을 직접 참조할 수 있게 된다.

이렇게 커스텀 데코레이터를 활용하면 코드의 의존성을 줄이고 더욱 유연하고 테스트를 하기 쉬운 구조로 만들 수 있다.

토큰을 다루는 또 다른 예시로, 이번에는 JWT 토큰이 응답 객체에 저장되어 있다고 가정해보자.

이 상황에선 createParamDecorator를 사용해 커스텀 데코레이터를 만들어 응답 객체에서 직접 토큰을 추출할 수 있다.

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const Token = createParamDecorator(
	(data: unknown, ctx: ExecutionContext) => {
		const response = ctx.switchToHttp().getResponse();
		return response.locals.jwt;
	}
)

위 코드를 통해 @Token() 데코레이터를 생성하게 되면, 컨트롤러 내에서 @Token() token과 같이 사용해 JWT 토큰을 손쉽게 가져올 수 있다.

이 방법은 req.userres.locals.jwt와 같은 방법을 사용하지 않음으로써 발생할 수 있는 문제점들을 피할 수 있는 중요한 접근 방식이다.

특히 만약에, req.userres.locals.jwt를 컨트롤러에서 많이 사용한다면 나중에 테스트가 필요하거나, Express에서 Fastify나 Koa와 같은 다른 플랫폼으로 전환할 때 많은 수정이 필요할 수 있다.

각각의 req.userres.locals.jwt가 사용된 부분을 일일이 찾아 수정해야 하는 번거로움이 발생한다.

하지만 커스텀 데코레이터를 사용함으로써 필요한 수정 사항을 훨씬 쉽게 관리할 수 있게 되며, 코드 중복도 줄일 수가 있다.

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const Token = createParamDecorator(
	(data: unknown, ctx: ExecutionContext) => {
		const response = ctx.switchToHttp().getResponse();
		return response.locals.jwt;
	}
)

그리고 여기서 언급된 ExecutionContext(실행 컨텍스트)는 복잡하게 생각할 필요가 없는 개념이다.

ctx 객체를 통해 switchToHttp 같은 메소드를 사용해 현재 HTTP 서버의 요청과 응답 객체 등을 접근할 수 있다.

NestJS는 HTTP 서버뿐만 아니라 웹소켓과 RPC 서버도 동시에 운영할 수 있는 유연성을 제공한다.

하나의 실행 컨텍스트 안에서 HTTP, 웹소켓, RPC 서버에 대한 정보를 관리하고 접근할 수 있다는 것은 개발의 편의성을 크게 높여준다.

이러한 구조 덕분에 하나의 서버에서 여러 통신 방식을 효율적으로 관리하고, 서로 다른 서버 유형 간의 소통을 용이하게 하는 것이 가능해진다.

NestJS가 이런 방식을 채택한 이유는 바로 이런 유연성확장성 때문이다.

커스텀 데코레이터를 통한 접근 방식은 이런 아키텍처를 더욱 활용할 수 있게 해준다.

결론

NestJS에서 커스텀 데코레이터 사용은 req.userres.locals.jwt와 같은 플랫폼에 종속적인 코드 사용을 피할 수 있게 해주고 코드의 유연성과 테스트 용이성을 향상시킨다.

@User()@Token() 같은 커스텀 데코레이터를 통해 컨트롤러 내에서 필요한 사용자의 정보나 토큰을 직접적이고 깔끔하게 접근할 수 있다.

이 방식은 플랫폼 변경 시 수정해야하는 코드의 양을 줄여주고 코드의 중복을 제거하는 효과를 가진다.

또한, NestJS의 실행 컨텍스트를 활용해 HTTP, 웹소켓, RPC 서버 간의 통신을 하나의 컨텍스트 내에서 효율적으로 관리할 수 있게 해준다.

profile
프론트엔드 개발자..일걸요?

0개의 댓글

관련 채용 정보