Nest는 TypeScript의 데코레이터를 기반으로 한 Custom 데코레이터를 더 쉽게 사용할 수 있도록 기능을 제공한다.
이를 활용하면 비즈니스 로직과 관련 없는 부분을 숨기고, 공통 처리를 통해 중복 코드를 줄이며 재사용성을 높일 수 있다.
이번에는 @User() Custom 데코레이터를 생성하여, 필요한 컨트롤러에서 유저 정보를 간편하게 가져올 수 있도록 하겠다.
먼저, Custom decorator에 대한 이해를 돕기 위한 간단한 예제이다.
이 예제는 Nest 공식 문서에서 확인할 수 있다.
https://docs.nestjs.com/custom-decorators
@Get()
getProfile(@Req() req) {
const user = req.user;
}
기존에는 사용자 정보를 필요로 하는 각 컨트롤러에서 매번 요청 객체(request)에서 데이터를 수동으로 추출해야 했다.
// user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
@Get()
getProfile(@User() user) {
console.log(user);
}
createParamDecorator를 이용해 @User() Custom 데코레이터를 만들어서 간편하게 사용할 수 있다.
이번 글에서는 요청 객체(Request)에 user 데이터를 첨부하는 방법과 함께, 유저 인증 과정도 다룰 예정이다.
이를 통해 '이미 검증된 온전한 User 데이터'가 필요한 곳에서 @User() 데코레이터만 사용하면 되도록 만드는 것이 목표다.
예제에서는 인증을 위해서 JWT 토큰을 사용한다.
방법1. 가드(Guard)와 함께 사용
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Request } from 'express';
import { JwtService } from '@nestjs/jwt';
import { UserService } from '../user/user.service';
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(
private readonly jwtService: JwtService,
private readonly userService: UserService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request: Request = context.switchToHttp().getRequest();
const authHeader = request.headers.authorization;
if (!authHeader) return false;
const token = authHeader.split(' ')[1];
if (!token) return false;
try {
const decoded = this.jwtService.verify(token, { secret: process.env.JWT_SECRET });
const user = await this.userService.findOne(decoded.userId);
if (!user) return false;
request.user = user; // 유저 정보를 요청 객체에 저장
return true;
} catch (error) {
return false;
}
}
}
@UseGuards(JwtAuthGuard) // 가드 적용
@Get()
getProfile(@User() user) {
console.log(user);
}
이 방법에는 한 가지 문제점이 있다.
@User() 데코레이터를 기대한 대로 사용하려면 반드시 앞서 정의한 가드와 함께 적용해야 한다. 그러나 이 과정에서 가드 적용 코드를 빠트리는 실수(휴먼 에러) 가 발생할 가능성이 있다.
방법2. @USER() 데코레이터 단독 사용
이 방법은 Custom 데코레이터를 생성하는 과정에서 유저 정보를 함께 저장하는 방식이다.
하지만 여기에는 한 가지 문제가 있다. 데코레이터 내부에서는 Provider에 직접 접근할 수 없으므로, 다음과 같은 일반적인 코드로는 유저 데이터를 가져올 수 없다.
const user = await this.userService.findOne(userId);
이 때, 사용할 수 있는 방법으로는 Provider를 글로벌 변수로 저장하고 필요한 곳에서 사용하는 것이다.
1. 글로벌 변수 ProviderRegistry 생성
class ProviderRegistry {
private static providers = new Map<string, any>();
static set(name: string, provider: any) {
this.providers.set(name, provider);
}
static get(name: string) {
return this.providers.get(name);
}
}
set()으로 Provider를 저장하고 get()으로 가져올 수 있다.
2. Provider를 글로벌 변수에 저장
import { Module, OnModuleInit } from '@nestjs/common';
import { UserService } from './user.service';
export class AppModule implements OnModuleInit {
constructor(private readonly userService: UserService) {}
onModuleInit() {
ProviderRegistry.set('UserService', this.userService);
}
}
OnModuleInit() 에서 사용할 Provider를 ProviderRegistry에 등록한다.
3. 데코리에터에서 Provider 사용
// user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
async (data: unknown, ctx: ExecutionContext) => {
...
const userService: UserService = ProviderRegistry.get('UserService');
const user = await userService.findOne(decoded.userId);
return user;
},
);
@Get()
getProfile(@User() user) {
console.log(user);
}
이제 원하는 대로 @User() 데코레이터 하나만 사용하여 유저 정보를 가져올 수 있게 되었다.
다만, 이 방식에는 Nest가 ProviderRegistry를 추적하지 못한다는 점이 있지만, 이 점이 큰 문제는 아닐 수 있다.
물론, 프로바이더를 글로벌 변수로 등록하는 추가 작업이 필요하지만, 인증된 유저 데이터가 필요할 때 @User() 데코레이터 하나만 사용하면 된다는 점에서 괜찮은 방법이라고 생각한다.
마무리
Nest에서 제공하는 createParamDecorator 뿐만 아니라, Reflector.createDecorator를 사용하여 원하는 데코레이터를 만들 수 있다.
이를 통해 중복 코드를 줄이고, 공통 관심사를 처리하여 코드를 모듈 단위로 효율적으로 관리할 수 있다.
https://docs.nestjs.com/fundamentals/execution-context#reflection-and-metadata
Documentation | NestJS - A progressive Node.js framework
Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea
docs.nestjs.com
https://www.typescriptlang.org/ko/docs/handbook/decorators.html
Documentation - Decorators
TypeScript Decorators overview
www.typescriptlang.org
'JavaScript' 카테고리의 다른 글
| NestJS에서 AsyncLocalStorage로 traceId 추적하기 (0) | 2026.05.23 |
|---|---|
| TypeORM의 save() 사용 시 데이터 변경 감지 원리 이해하기 (0) | 2024.12.15 |
| 프로토타입 (0) | 2024.05.22 |
| this / 콜백 함수 / 클로저 (0) | 2024.05.20 |
| 자바스크립트의 실행 컨텍스트 (0) | 2024.05.19 |