๋ชจ๋ ์ง์ฐ ๋ก๋ฉ
๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋์ ์ฆ์ ๋ก๋ (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
'๐ ๊ณต์ ๋ฌธ์ ๋ฒ์ญ > Nest.js' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Nest.js] Fundamentals | Platform agnosticism, Testing (3) | 2025.07.29 |
---|---|
[Nest.js] Fundamentals | Lifecycle events, Discovery service (1) | 2025.07.29 |
[Nest.js] Fundamentals - Circular dependency, Module reference (0) | 2025.07.19 |
[Nest.js] Fundamentals - Dynamic modules, Injection scopes (0) | 2025.07.16 |
[Nest.js] Fundamentals - Custom providers, Asynchronous providers (0) | 2025.07.15 |