๐Ÿ“‘ ๊ณต์‹ ๋ฌธ์„œ ๋ฒˆ์—ญ/Nest.js

[Nest.js] Fundamentals | Lazy-loading modules, Execution context

dev_writer 2025. 7. 29. 00:42

๋ชจ๋“ˆ ์ง€์—ฐ ๋กœ๋”ฉ

๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“ˆ์€ ์ฆ‰์‹œ ๋กœ๋“œ (eagerly loaded) ๋˜๋ฉฐ, ์ด๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘๋˜๋ฉด ๋ชจ๋“  ๋ชจ๋“ˆ์ด ์ฆ‰์‹œ ๋กœ๋“œ๋œ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋Š” ์ด ๋ฐฉ์‹์œผ๋กœ ์ถฉ๋ถ„ํ•˜์ง€๋งŒ, ์„œ๋ฒ„๋ฆฌ์Šค ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰๋˜๋Š” ์•ฑ์ด๋‚˜ ์›Œ์ปค์—์„œ๋Š” ์‹œ์ž‘ ์ง€์—ฐ ์‹œ๊ฐ„ ("์ฝœ๋“œ ์Šคํƒ€ํŠธ")์ด ์ค‘์š”ํ•˜๋ฏ€๋กœ ๋ณ‘๋ชฉ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ง€์—ฐ ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • ์„œ๋ฒ„๋ฆฌ์Šค ํ•จ์ˆ˜ ํ˜ธ์ถœ์— ํ•„์š”ํ•œ ๋ชจ๋“ˆ๋งŒ ๋กœ๋“œํ•จ์œผ๋กœ์จ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ์‹œ๊ฐ„์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์„œ๋ฒ„๋ฆฌ์Šค ํ•จ์ˆ˜๊ฐ€ "์›œ (warm)" ์ƒํƒœ๊ฐ€ ๋˜์—ˆ์„ ๋•Œ ๋‹ค๋ฅธ ๋ชจ๋“ˆ์„ ๋น„๋™๊ธฐ๋กœ ๋กœ๋“œํ•จ์œผ๋กœ์จ ์ดํ›„ ํ˜ธ์ถœ์˜ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ์‹œ๊ฐ„์„ ๋”์šฑ ๋‹จ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์ง€์—ฐ๋œ ๋ชจ๋“ˆ ๋“ฑ๋ก).

ํžŒํŠธ
Angular ํ”„๋ ˆ์ž„์›Œํฌ์— ์ต์ˆ™ํ•˜๋‹ค๋ฉด "lazy-loading modules (์ง€์—ฐ ๋กœ๋”ฉ ๋ชจ๋“ˆ)"์ด๋ผ๋Š” ์šฉ์–ด๋ฅผ ๋ณธ ์ ์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ Nest์—์„œ๋Š” ์ด ๊ธฐ์ˆ ์ด ๊ธฐ๋Šฅ์ ์œผ๋กœ ๋‹ค๋ฅด๋ฏ€๋กœ, ๋‹จ์ง€ ์ด๋ฆ„๋งŒ ๋น„์Šทํ•  ๋ฟ ์™„์ „ํžˆ ๋‹ค๋ฅธ ๊ธฐ๋Šฅ์œผ๋กœ ์ดํ•ดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ฃผ์˜
์ง€์—ฐ ๋กœ๋”ฉ๋œ ๋ชจ๋“ˆ ๋ฐ ์„œ๋น„์Šค์—์„œ๋Š” ๋ผ์ดํ”„์‚ฌ์ดํด ํ›… ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”.

์‹œ์ž‘ํ•˜๊ธฐ

Nest์—์„œ๋Š” LazyModuleLoader ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด ํ•„์š”ํ•  ๋•Œ ๋ชจ๋“ˆ์„ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ผ๋ฐ˜์ ์ธ ๋ฐฉ์‹์œผ๋กœ ํด๋ž˜์Šค์— ์ฃผ์ž…ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Injectable()
export class CatsService {
  constructor(private lazyModuleLoader: LazyModuleLoader) {}
}
ํžŒํŠธ
LazyModuleLoader ํด๋ž˜์Šค๋Š” @nestjs/core ํŒจํ‚ค์ง€์—์„œ import ๋ฉ๋‹ˆ๋‹ค.

 

๋˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ํŒŒ์ผ (main.ts) ๋‚ด์—์„œ LazyModuleLoader ํ”„๋กœ๋ฐ”์ด๋”์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์–ป์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค:

// "app"์€ Nest ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
const lazyModuleLoader = app.get(LazyModuleLoader);

 

์ด์ œ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์›ํ•˜๋Š” ๋ชจ๋“ˆ์„ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

const { LazyModule } = await import('./lazy.module');
const moduleRef = await this.lazyModuleLoader.load(() => LazyModule);
ํžŒํŠธ
"์ง€์—ฐ ๋กœ๋”ฉ๋œ" ๋ชจ๋“ˆ์€ ์ตœ์ดˆ LazyModuleLoader#load ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ์บ์‹œ์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ดํ›„ ๋™์ผํ•œ ๋ชจ๋“ˆ์„ ๋กœ๋“œํ•˜๋ ค๋Š” ๋ชจ๋“  ์‹œ๋„๊ฐ€ ๋งค์šฐ ๋น ๋ฅด๊ฒŒ ์บ์‹œ ๋œ ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ๋ชจ๋“ˆ์„ ๋‹ค์‹œ ๋กœ๋“œํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
Load "LazyModule" attempt: 1
time: 2.379ms
Load "LazyModule" attempt: 2
time: 0.294ms
Load "LazyModule" attempt: 3
time: 0.303msโ€‹

๋˜ํ•œ "์ง€์—ฐ ๋กœ๋”ฉ๋œ" ๋ชจ๋“ˆ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ์‹œ ์ฆ‰์‹œ ๋กœ๋“œ๋œ ๋ชจ๋“ˆ๋“ค๊ณผ ์ดํ›„์— ๋“ฑ๋ก๋œ ๋‹ค๋ฅธ ์ง€์—ฐ ๋กœ๋”ฉ ๋ชจ๋“ˆ๋“ค๊ณผ ๋™์ผํ•œ ๋ชจ๋“ˆ ๊ทธ๋ž˜ํ”„๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.

 

lazy.module.ts ๋Š” ์ผ๋ฐ˜ Nest ๋ชจ๋“ˆ์„ export ํ•˜๋Š” TypeScript ํŒŒ์ผ์ด๋ฉฐ, ๋ณ„๋„์˜ ์ถ”๊ฐ€ ๋ณ€๊ฒฝ์€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

LazyModuleLoader#load ๋ฉ”์„œ๋“œ๋Š” LazyModule์˜ ๋ชจ๋“ˆ ์ฐธ์กฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ๋‚ด๋ถ€์˜ ํ”„๋กœ๋ฐ”์ด๋” ๋ชฉ๋ก์„ ํƒ์ƒ‰ํ•˜๊ณ , ์ฃผ์ž… ํ† ํฐ์„ ์กฐํšŒ ํ‚ค๋กœ ์‚ฌ์šฉํ•˜์—ฌ ์›ํ•˜๋Š” ํ”„๋กœ๋ฐ”์ด๋”์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ •์˜๋ฅผ ๊ฐ€์ง„ LazyModule์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค:

@Module({
  providers: [LazyService],
  exports: [LazyService],
})
export class LazyModule {}
ํžŒํŠธ
์ง€์—ฐ ๋กœ๋”ฉ๋œ ๋ชจ๋“ˆ์€ ์ „์—ญ ๋ชจ๋“ˆ๋กœ ๋“ฑ๋กํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋ชจ๋“  ์ •์  ๋ชจ๋“ˆ์ด ์ด๋ฏธ ์ธ์Šคํ„ด์Šคํ™”๋œ ์ดํ›„์— ํ•„์š”์‹œ ๋กœ๋“œ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ „์—ญ ๋“ฑ๋ก์ด ์˜๋ฏธ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์ „์—ญ์œผ๋กœ ๋“ฑ๋ก๋œ enhancer๋“ค (guard, interceptor ๋“ฑ)๋„ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

์ด์ œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด LazyService ํ”„๋กœ๋ฐ”์ด๋”์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const { LazyModule } = await import('./lazy.module');
const moduleRef = await this.lazyModuleLoader.load(() => LazyModule);

const { LazyService } = await import('./lazy.service');
const lazyService = moduleRef.get(LazyService);
์ฃผ์˜
Webpack์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ tsconfig.json ํŒŒ์ผ์„ ๋ฐ˜๋“œ์‹œ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. compilerOptions.module์„ "esnext"๋กœ ์„ค์ •ํ•˜๊ณ , compilerOptions.moduleResolution ์†์„ฑ์— "node" ๊ฐ’์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”:
{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "node",
    ...
  }
}โ€‹


์ด ์„ค์ •์„ ํ†ตํ•ด ์ฝ”๋“œ ๋ถ„ํ•  (code splitting) ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ปจํŠธ๋กค๋Ÿฌ, ๊ฒŒ์ดํŠธ์›จ์ด, ๋ฆฌ์กธ๋ฒ„์˜ ์ง€์—ฐ ๋กœ๋”ฉ

Nest์—์„œ ์ปจํŠธ๋กค๋Ÿฌ (๋˜๋Š” GraphQL ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฆฌ์กธ๋ฒ„)๋Š” ๊ฒฝ๋กœ/๋ผ์šฐํŠธ/ํ† ํ”ฝ (๋˜๋Š” ์ฟผ๋ฆฌ/๋ฎคํ…Œ์ด์…˜)์˜ ์ง‘ํ•ฉ์„ ๋‚˜ํƒ€๋‚ด๋ฏ€๋กœ, LazyModuleLoader ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋“ค์„ ์ง€์—ฐ ๋กœ๋”ฉํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค.

์ฃผ์˜
์ง€์—ฐ ๋กœ๋”ฉ๋œ ๋ชจ๋“ˆ ๋‚ด๋ถ€์— ๋“ฑ๋ก๋œ ์ปจํŠธ๋กค๋Ÿฌ, ๋ฆฌ์กธ๋ฒ„, ๊ฒŒ์ดํŠธ์›จ์ด๋Š” ์˜ˆ์ƒ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ MiddlewareConsumer ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์˜จ๋””๋งจ๋“œ ๋ฐฉ์‹์œผ๋กœ ๋“ฑ๋กํ•˜๋Š” ๊ฒƒ๋„ ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

 

์˜ˆ๋ฅผ ๋“ค์–ด, Fastify ๋“œ๋ผ์ด๋ฒ„ (@nestjs/platform-fastify ํŒจํ‚ค์ง€ ์‚ฌ์šฉ)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ REST API (HTTP ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜)๋ฅผ ๊ตฌ์ถ•ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค. Fastify๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ค€๋น„๋˜๊ณ  ๋ฉ”์‹œ์ง€๋ฅผ ์ˆ˜์‹ ํ•˜๊ธฐ ์‹œ์ž‘ํ•œ ์ดํ›„์—๋Š” ๋ผ์šฐํŠธ๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ฆ‰, ๋ชจ๋“ˆ์˜ ์ปจํŠธ๋กค๋Ÿฌ์— ๋“ฑ๋ก๋œ ๋ผ์šฐํŠธ ๋งคํ•‘์„ ๋ถ„์„ํ•˜๋”๋ผ๋„, ์ง€์—ฐ ๋กœ๋”ฉ๋œ ๋ผ์šฐํŠธ๋Š” ๋Ÿฐํƒ€์ž„์— ๋“ฑ๋กํ•  ๋ฐฉ๋ฒ•์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

 

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, @nestjs/microservices ํŒจํ‚ค์ง€์—์„œ ์ œ๊ณตํ•˜๋Š” ์ผ๋ถ€ ์ „์†ก ์ „๋žต (์˜ˆ: Kafka, gRPC, RabbitMQ ๋“ฑ)์€ ์—ฐ๊ฒฐ์ด ์„ค์ •๋˜๊ธฐ ์ „์— ํŠน์ • ํ† ํ”ฝ/์ฑ„๋„์— ๋Œ€ํ•œ ๊ตฌ๋… ๋ฐ ์ˆ˜์‹  ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ ์„ ์‹œ์ž‘ํ•œ ์ดํ›„์—๋Š” ์ƒˆ๋กœ์šด ํ† ํ”ฝ์— ๋Œ€ํ•ด ๊ตฌ๋…/์ˆ˜์‹ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

 

๋งˆ์ง€๋ง‰์œผ๋กœ, @nestjs/graphql ํŒจํ‚ค์ง€์—์„œ code-first ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ GraphQL ์Šคํ‚ค๋งˆ๊ฐ€ ๋™์ ์œผ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋ชจ๋“  ๊ด€๋ จ ํด๋ž˜์Šค๊ฐ€ ์‚ฌ์ „์— ๋กœ๋“œ๋˜์–ด ์žˆ์–ด์•ผ ํ•จ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์˜ฌ๋ฐ”๋ฅธ ์Šคํ‚ค๋งˆ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€

์ง€์—ฐ ๋กœ๋”ฉ ๋ชจ๋“ˆ์€ ์ฃผ๋กœ ์›Œ์ปค, ํฌ๋ก  ์žก, ๋žŒ๋‹ค ๋ฐ ์„œ๋ฒ„๋ฆฌ์Šค ํ•จ์ˆ˜, ์›นํ›… ๋“ฑ์ด ์ž…๋ ฅ ์ธ์ž (๋ผ์šฐํŠธ ๊ฒฝ๋กœ, ๋‚ ์งœ, ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๋“ฑ)์— ๋”ฐ๋ผ ์„œ๋กœ ๋‹ค๋ฅธ ์„œ๋น„์Šค (๋‹ค๋ฅธ ๋กœ์ง)๋ฅผ ์‹คํ–‰ํ•ด์•ผ ํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ๊ฐ„์ด ํฌ๊ฒŒ ์ค‘์š”ํ•˜์ง€ ์•Š์€ ๋ชจ๋†€๋ฆฌ์‹ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ์ง€์—ฐ ๋กœ๋”ฉ ๋ชจ๋“ˆ์ด ํฐ ์˜๋ฏธ๋ฅผ ๊ฐ–์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹คํ–‰ ์ปจํ…์ŠคํŠธ

Nest๋Š” ์—ฌ๋Ÿฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ (์˜ˆ: Nest HTTP ์„œ๋ฒ„ ๊ธฐ๋ฐ˜, ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค, WebSocket ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ) ์—์„œ ๋™์ž‘ํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” ์—ฌ๋Ÿฌ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋Š” ํ˜„์žฌ ์‹คํ–‰ ์ปจํ…์ŠคํŠธ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ์ด๋ฅผ ํ™œ์šฉํ•ด ๋‹ค์–‘ํ•œ ์ปจํŠธ๋กค๋Ÿฌ, ๋ฉ”์„œ๋“œ, ์‹คํ–‰ ์ปจํ…์ŠคํŠธ ์ „๋ฐ˜์—์„œ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฒ”์šฉ์ ์ธ ๊ฐ€๋“œ, ํ•„ํ„ฐ, ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ด๋ฒˆ ์žฅ์—์„œ๋Š” ArgumentsHost์™€ ExecutionContext๋ผ๋Š” ๋‘ ๊ฐ€์ง€ ํด๋ž˜์Šค๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

ArgumentsHost ํด๋ž˜์Šค

ArgumentsHost ํด๋ž˜์Šค๋Š” ํ•ธ๋“ค๋Ÿฌ์— ์ „๋‹ฌ๋˜๋Š” ์ธ์ž๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์–ด๋–ค ์ปจํ…์ŠคํŠธ (์˜ˆ: HTTP, RPC (๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค), WebSocket)์—์„œ ์ธ์ž๋ฅผ ๊ฐ€์ ธ์˜ฌ์ง€ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Nest ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ArgumentsHost ์ธ์Šคํ„ด์Šค๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ์ผ๋ฐ˜์ ์œผ๋กœ host๋ผ๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ฐธ์กฐ๋˜๋ฉฐ, ์˜ˆ์™ธ ํ•„ํ„ฐ์˜ catch() ๋ฉ”์„œ๋“œ์ฒ˜๋Ÿผ ์ ‘๊ทผ์ด ํ•„์š”ํ•œ ๊ณณ์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

 

ArgumentsHost๋Š” ๋‹จ์ˆœํžˆ ํ•ธ๋“ค๋Ÿฌ ์ธ์ž์— ๋Œ€ํ•œ ์ถ”์ƒํ™” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, HTTP ์„œ๋ฒ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ (@nestjs/platform-express ์‚ฌ์šฉ ์‹œ)์—๋Š” host ๊ฐ์ฒด๊ฐ€ Express์˜ [request, response, next] ๋ฐฐ์—ด์„ ์บก์Аํ™”ํ•˜๋ฉฐ, ์—ฌ๊ธฐ์„œ request๋Š” ์š”์ฒญ ๊ฐ์ฒด, response๋Š” ์‘๋‹ต ๊ฐ์ฒด, next๋Š” ์š”์ฒญ-์‘๋‹ต ์‚ฌ์ดํด์„ ์ œ์–ดํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด, GraphQL ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” host ๊ฐ์ฒด๊ฐ€ [root, args, context, info] ๋ฐฐ์—ด์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

ํ˜„์žฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ

์—ฌ๋Ÿฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ ์ „๋ฐ˜์—์„œ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋Š” ๋ฒ”์šฉ ๊ฐ€๋“œ, ํ•„ํ„ฐ, ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ๋Š”, ํ˜„์žฌ ๋ฉ”์„œ๋“œ๊ฐ€ ์–ด๋–ค ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํƒ€์ž…์—์„œ ์‹คํ–‰๋˜๊ณ  ์žˆ๋Š”์ง€๋ฅผ ํŒ๋ณ„ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ArgumentsHost์˜ getType() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

if (host.getType() === 'http') {
  // ์ผ๋ฐ˜ HTTP ์š”์ฒญ(REST) ์ปจํ…์ŠคํŠธ์—์„œ๋งŒ ์ค‘์š”ํ•œ ์ž‘์—… ์ˆ˜ํ–‰
} else if (host.getType() === 'rpc') {
  // ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์š”์ฒญ ์ปจํ…์ŠคํŠธ์—์„œ๋งŒ ์ค‘์š”ํ•œ ์ž‘์—… ์ˆ˜ํ–‰
} else if (host.getType<GqlContextType>() === 'graphql') {
  // GraphQL ์š”์ฒญ ์ปจํ…์ŠคํŠธ์—์„œ๋งŒ ์ค‘์š”ํ•œ ์ž‘์—… ์ˆ˜ํ–‰
}
ํžŒํŠธ
GqlContextType์€ @nestjs/graphql ํŒจํ‚ค์ง€์—์„œ import ๋ฉ๋‹ˆ๋‹ค.

 

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํƒ€์ž…์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณด๋‹ค ๋ฒ”์šฉ์ ์ธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ˜ธ์ŠคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋งค๊ฐœ๋ณ€์ˆ˜

ํ˜ธ์ŠคํŠธ ๊ฐ์ฒด์˜ getArgs() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ํ•ธ๋“ค๋Ÿฌ์— ์ „๋‹ฌ๋œ ์ธ์ž์˜ ๋ฐฐ์—ด์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const [req, res, next] = host.getArgs();

 

getArgByIndex() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • ์ธ๋ฑ์Šค์˜ ์ธ์ž๋งŒ ์„ ํƒ์ ์œผ๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const request = host.getArgByIndex(0);
const response = host.getArgByIndex(1);

 

์ด ์˜ˆ์ œ๋“ค์—์„œ๋Š” ์š”์ฒญ ๋ฐ ์‘๋‹ต ๊ฐ์ฒด๋ฅผ ์ธ๋ฑ์Šค๋กœ ๊ฐ€์ ธ์™”์ง€๋งŒ, ์ด๋Š” ํŠน์ • ์‹คํ–‰ ์ปจํ…์ŠคํŠธ์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๊ฒฐํ•ฉ๋˜๋ฏ€๋กœ ์ผ๋ฐ˜์ ์œผ๋กœ ๊ถŒ์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹ , ํ˜ธ์ŠคํŠธ ๊ฐ์ฒด์˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ˜„์žฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ ํ•ฉํ•œ ์‹คํ–‰ ์ปจํ…์ŠคํŠธ๋กœ ์ „ํ™˜ํ•จ์œผ๋กœ์จ ์ฝ”๋“œ๋ฅผ ๋”์šฑ ๊ฒฌ๊ณ ํ•˜๊ณ  ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ์ด๋Ÿฌํ•œ ์ปจํ…์ŠคํŠธ ์ „ํ™˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.

/**
 * RPC ์ปจํ…์ŠคํŠธ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
 */
switchToRpc(): RpcArgumentsHost;
/**
 * HTTP ์ปจํ…์ŠคํŠธ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
 */
switchToHttp(): HttpArgumentsHost;
/**
 * WebSocket ์ปจํ…์ŠคํŠธ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
 */
switchToWs(): WsArgumentsHost;

 

์•ž์„  ์˜ˆ์ œ๋ฅผ switchToHttp() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์‹œ ์ž‘์„ฑํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. host.switchToHttp() ํ˜ธ์ถœ์€ HTTP ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ์— ์ ํ•ฉํ•œ HttpArgumentsHost ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. HttpArgumentsHost ๊ฐ์ฒด์—๋Š” ์›ํ•˜๋Š” ๊ฐ์ฒด๋ฅผ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์šฉํ•œ ๋ฉ”์„œ๋“œ ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ์œผ๋ฉฐ, ์ด ๊ฒฝ์šฐ์—๋Š” Express ํƒ€์ž… ๋‹จ์–ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋„ค์ดํ‹ฐ๋ธŒ Express ํƒ€์ž… ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค:

const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();
const response = ctx.getResponse<Response>();

 

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ WsArgumentsHost์™€ RpcArgumentsHost๋„ ๊ฐ๊ฐ WebSocket ๋ฐ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์ปจํ…์ŠคํŠธ์— ์ ํ•ฉํ•œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” WsArgumentsHost์˜ ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค:

export interface WsArgumentsHost {
  /**
   * ๋ฐ์ดํ„ฐ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
   */
  getData<T>(): T;
  /**
   * ํด๋ผ์ด์–ธํŠธ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
   */
  getClient<T>(): T;
}

 

๋‹ค์Œ์€ RpcArgumentsHost์˜ ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค:

export interface RpcArgumentsHost {
  /**
   * ๋ฐ์ดํ„ฐ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
   */
  getData<T>(): T;

  /**
   * ์ปจํ…์ŠคํŠธ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
   */
  getContext<T>(): T;
}

 

ExecutionContext ํด๋ž˜์Šค๋Š” ArgumentsHost๋ฅผ ์ƒ์†ํ•˜๋ฉฐ, ํ˜„์žฌ ์‹คํ–‰ ํ”„๋กœ์„ธ์Šค์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ArgumentsHost์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, Nest๋Š” canActive() ๋ฉ”์„œ๋“œ (guard์—์„œ)๋‚˜ intercept() ๋ฉ”์„œ๋“œ (interceptor์—์„œ)์™€ ๊ฐ™์€ ํ•„์š”ํ•œ ๊ณณ์— ExecutionContext ์ธ์Šคํ„ด์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ํด๋ž˜์Šค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

export interface ExecutionContext extends ArgumentsHost {
  /**
   * ํ˜„์žฌ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์†ํ•œ ์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค์˜ ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
   */
  getClass<T>(): Type<T>;
  /**
   * ์š”์ฒญ ํŒŒ์ดํ”„๋ผ์ธ์—์„œ ๋‹ค์Œ์œผ๋กœ ํ˜ธ์ถœ๋  ํ•ธ๋“ค๋Ÿฌ(๋ฉ”์„œ๋“œ)๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
   */
  getHandler(): Function;
}

 

getHandler() ๋ฉ”์„œ๋“œ๋Š” ๊ณง ํ˜ธ์ถœ๋  ํ•ธ๋“ค๋Ÿฌ์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. getClass() ๋ฉ”์„œ๋“œ๋Š” ํ•ด๋‹น ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์†ํ•œ ์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค์˜ ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, HTTP ์ปจํ…์ŠคํŠธ์—์„œ ํ˜„์žฌ ์ฒ˜๋ฆฌ ์ค‘์ธ ์š”์ฒญ์ด CatsController์˜ create() ๋ฉ”์„œ๋“œ์— ๋ฐ”์ธ๋”ฉ๋œ POST ์š”์ฒญ์ด๋ผ๋ฉด, getHandler()๋Š” create() ๋ฉ”์„œ๋“œ์˜ ์ฐธ์กฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ , getClass()๋Š” CatsController ํด๋ž˜์Šค (์ธ์Šคํ„ด์Šค๊ฐ€ ์•„๋‹˜)๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

const methodKey = ctx.getHandler().name; // "create"
const className = ctx.getClass().name; // "CatsController"

 

ํ˜„์žฌ ํด๋ž˜์Šค์™€ ํ•ธ๋“ค๋Ÿฌ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ์ฐธ์กฐ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์€ ํฐ ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ฌด์—‡๋ณด๋‹ค๋„, guard๋‚˜ interceptor ๋‚ด๋ถ€์—์„œ Reflector#createDecorator๋กœ ์ƒ์„ฑ๋œ ์ปค์Šคํ…€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋‚˜ Nest ๋‚ด์žฅ @SetMetadata() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ ์„ค์ •๋œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐํšŒ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํ™œ์šฉ ์‚ฌ๋ก€๋Š” ์•„๋ž˜์—์„œ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

๋ฆฌํ”Œ๋ ‰์…˜๊ณผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ

Nest๋Š” Reflector#createDecorator ๋ฉ”์„œ๋“œ๋กœ ์ƒ์„ฑํ•œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์™€ ๋‚ด์žฅ @SetMetadata() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ํ†ตํ•ด ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์— ์ปค์Šคํ…€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋ถ€์ฐฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ์„น์…˜์—์„œ๋Š” ๋‘ ๊ฐ€์ง€ ์ ‘๊ทผ ๋ฐฉ์‹์„ ๋น„๊ตํ•˜๊ณ , ๊ฐ€๋“œ๋‚˜ ์ธํ„ฐ์…‰ํ„ฐ ๋‚ด๋ถ€์—์„œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

Reflector#createDecorator๋ฅผ ์‚ฌ์šฉํ•ด ํƒ€์ž…์ด ๊ฐ•์ œ๋œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค๋ ค๋ฉด, ํƒ€์ž… ์ธ์ž๋ฅผ ์ง€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฌธ์ž์—ด ๋ฐฐ์—ด์„ ์ธ์ž๋กœ ๋ฐ›๋Š” Roles ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

import { Reflector } from '@nestjs/core';

export const Roles = Reflector.createDecorator<string[]>();

 

์—ฌ๊ธฐ์„œ Roles ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” string[] ํƒ€์ž…์˜ ์ธ์ž๋ฅผ ํ•˜๋‚˜ ๋ฐ›๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

 

์ด์ œ ์ด ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด, ๋‹จ์ˆœํžˆ ํ•ธ๋“ค๋Ÿฌ์— ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค:

@Post()
@Roles(['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

 

์—ฌ๊ธฐ์„œ๋Š” create() ๋ฉ”์„œ๋“œ์— Roles ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋ถ€์ฐฉํ•˜์—ฌ, ์˜ค์ง admin ์—ญํ• ์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๋งŒ ์ด ๋ผ์šฐํŠธ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Œ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

 

์ด์ œ ํ•ด๋‹น ๋ผ์šฐํŠธ์˜ ์—ญํ•  (์ปค์Šคํ…€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ)์— ์ ‘๊ทผํ•˜๋ ค๋ฉด, Reflector ํ—ฌํผ ํด๋ž˜์Šค๋ฅผ ๋‹ค์‹œ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. Reflector๋Š” ์ผ๋ฐ˜์ ์ธ ๋ฐฉ์‹์œผ๋กœ ํด๋ž˜์Šค์— ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

@Injectable()
export class RolesGuard {
  constructor(private reflector: Reflector) {}
}
ํžŒํŠธ
Reflector ํด๋ž˜์Šค๋Š” @nestjs/core ํŒจํ‚ค์ง€์—์„œ import ๋ฉ๋‹ˆ๋‹ค.

 

์ด์ œ ํ•ธ๋“ค๋Ÿฌ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์œผ๋ ค๋ฉด, get() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค:

const roles = this.reflector.get(Roles, context.getHandler());

 

Reflector#get ๋ฉ”์„œ๋“œ๋Š” ๋‘ ๊ฐœ์˜ ์ธ์ž๋ฅผ ๋ฐ›์•„ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์— ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด ์ค๋‹ˆ๋‹ค: ํ•˜๋‚˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์ฐธ์กฐ์ด๊ณ , ๋‹ค๋ฅธ ํ•˜๋‚˜๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•  ๋Œ€์ƒ (context)์ž…๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ์—์„œ๋Š” Roles ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ (์œ„์˜ roles.decorator.ts ํŒŒ์ผ ์ฐธ์กฐ)๊ฐ€ ์ง€์ •๋œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์ด๊ณ , context๋Š” context.getHandler() ํ˜ธ์ถœ์„ ํ†ตํ•ด ์ œ๊ณต๋˜๋ฉฐ, ์ด๋Š” ํ˜„์žฌ ์ฒ˜๋ฆฌ ์ค‘์ธ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์— ๋Œ€ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ฐธ๊ณ ๋กœ, getHandler()๋Š” ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

 

๋˜ํ•œ, ์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค์˜ ๋ชจ๋“  ๋ผ์šฐํŠธ์— ์ ์šฉ๋˜๋„๋ก ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ ์ˆ˜์ค€์—์„œ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

@Roles(['admin'])
@Controller('cats')
export class CatsController {}

 

์ด ๊ฒฝ์šฐ์—๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ ์ˆ˜์ค€์—์„œ ์ถ”์ถœํ•˜๊ธฐ ์œ„ํ•ด, context.getHandler() ๋Œ€์‹  context.getClass()๋ฅผ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•  context๋กœ ์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

const roles = this.reflector.get(Roles, context.getClass());

 

์—ฌ๋Ÿฌ ์ˆ˜์ค€์—์„œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์—ฌ๋Ÿฌ context๋กœ๋ถ€ํ„ฐ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•˜๊ณ  ๋ณ‘ํ•ฉํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๋•๊ธฐ ์œ„ํ•ด Reflector ํด๋ž˜์Šค๋Š” ๋‘ ๊ฐ€์ง€ ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋“ค์€ ์ปจํŠธ๋กค๋Ÿฌ์™€ ๋ฉ”์„œ๋“œ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ์ถ”์ถœํ•˜๊ณ , ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ๋ณ‘ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

 

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์„ ์ƒ๊ฐํ•ด ๋ด…์‹œ๋‹ค. Roles ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ์™€ ๋ฉ”์„œ๋“œ ์–‘์ชฝ์— ๋ชจ๋‘ ์„ค์ •ํ•œ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค.

@Roles(['user'])
@Controller('cats')
export class CatsController {
  @Post()
  @Roles(['admin'])
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }
}

 

๊ธฐ๋ณธ ์—ญํ• ์„ 'user'๋กœ ์ง€์ •ํ•˜๊ณ , ํŠน์ • ๋ฉ”์„œ๋“œ์—๋งŒ ์„ ํƒ์ ์œผ๋กœ ์ด๋ฅผ ๋ฎ์–ด์“ฐ๊ณ ์ž ํ•œ๋‹ค๋ฉด getAllAndOverride() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

const roles = this.reflector.getAllAndOverride(Roles, [context.getHandler(), context.getClass()]);

 

์ด ์ฝ”๋“œ๊ฐ€ create() ๋ฉ”์„œ๋“œ์˜ ์ปจํ…์ŠคํŠธ์—์„œ ์‹คํ–‰๋œ๋‹ค๋ฉด, roles๋Š” ['admin']์„ ํฌํ•จํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

์–‘์ชฝ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์™€ ๋ณ‘ํ•ฉํ•˜๋ ค๋ฉด(๋ฐฐ์—ด๊ณผ ๊ฐ์ฒด๋ฅผ ๋ชจ๋‘ ๋ณ‘ํ•ฉ), getAllAndMerge() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

const roles = this.reflector.getAllAndMerge(Roles, [context.getHandler(), context.getClass()]);

 

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด roles๋Š” ['user', 'admin']์„ ํฌํ•จํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

์ด ๋‘ ๊ฐ€์ง€ ๋ณ‘ํ•ฉ ๋ฉ”์„œ๋“œ ๋ชจ๋‘์—์„œ, ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ‚ค๋ฅผ ์ „๋‹ฌํ•˜๊ณ , ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋Œ€์ƒ ์ปจํ…์ŠคํŠธ์˜ ๋ฐฐ์—ด (์ฆ‰, getHandler() ๋ฐ ๋˜๋Š” getClass() ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ)์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

์ €์ˆ˜์ค€ ์ ‘๊ทผ

์•ž์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด, Reflector#createDecorator๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ , ๋‚ด์žฅ๋œ @SetMetadata() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ธ๋“ค๋Ÿฌ์— ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
ํžŒํŠธ
@SetMetadata() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” @nestjs/common ํŒจํ‚ค์ง€์—์„œ import ํ•ฉ๋‹ˆ๋‹ค.

 

์œ„์™€ ๊ฐ™์ด ๊ตฌ์„ฑํ•˜๋ฉด, roles ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ‚ค์— ['admin'] ๊ฐ’์„ ํ• ๋‹นํ•˜์—ฌ create() ๋ฉ”์„œ๋“œ์— ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์ž‘๋™์€ ํ•˜์ง€๋งŒ, @SetMetadata()๋ฅผ ๋ผ์šฐํŠธ์—์„œ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๊ถŒ์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉ์ž ์ •์˜ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

 

์ด ์ ‘๊ทผ ๋ฐฉ์‹์€ ํ›จ์”ฌ ๋” ๊น”๋”ํ•˜๊ณ  ๊ฐ€๋…์„ฑ๋„ ์ข‹์œผ๋ฉฐ, Reflector#createDecorator ๋ฐฉ์‹๊ณผ ์œ ์‚ฌํ•œ ๋ฉด์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ฐจ์ด์ ์€ @SetMetadata๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์˜ ํ‚ค์™€ ๊ฐ’์— ๋Œ€ํ•ด ๋” ๋งŽ์€ ์ œ์–ด๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ณ , ์ธ์ž๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.

 

์ด์ œ ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ์ปค์Šคํ…€ @Roles() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ create() ๋ฉ”์„œ๋“œ๋ฅผ ๋ฐ์ฝ”๋ ˆ์ดํŠธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

 

๋ผ์šฐํŠธ์˜ ์—ญํ•  (role) ์ •๋ณด (์ปค์Šคํ…€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ)์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด, ๋‹ค์‹œ Reflector ํ—ฌํผ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@Injectable()
export class RolesGuard {
  constructor(private reflector: Reflector) {}
}
ํžŒํŠธ
Reflector ํด๋ž˜์Šค๋Š” @nestjs/core ํŒจํ‚ค์ง€์—์„œ import ํ•ฉ๋‹ˆ๋‹ค.

 

์ด์ œ ํ•ธ๋“ค๋Ÿฌ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ธฐ ์œ„ํ•ด get() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

const roles = this.reflector.get<string[]>('roles', context.getHandler());

 

์—ฌ๊ธฐ์„œ๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์ฐธ์กฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋Œ€์‹ , ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ‚ค (์ด ๊ฒฝ์šฐ 'roles')๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋จธ์ง€๋Š” Reflector#createDecorator ์˜ˆ์ œ์™€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.

Reference