NestJS Overview 총정리

원민관·2026년 6월 29일

[TIL]

목록 보기
214/215
post-thumbnail

✍️ 0. NestJS Overview

공식 문서: https://docs.nestjs.com

NestJS는 Decorator, DI(Dependency Injection), IoC(Inversion of Control), AOP(Aspect Oriented Programming) 를 중심으로 설계된 Node.js 백엔드 프레임워크입니다.

개념Nest에서의 역할
Decorator클래스·메서드·파라미터에 메타데이터를 붙여 프레임워크가 구조를 읽게 합니다 (@Controller, @Injectable, @Roles 등)
IoC객체 생성·연결을 개발자가 아니라 Nest 컨테이너가 담당합니다 (new Service() 직접 호출 ❌)
DIIoC 컨테이너가 constructor에 필요한 의존성을 주입합니다
AOP로깅·인증·검증·에러 처리 같은 횡단 관심사를 handler 밖(Guard, Interceptor, Pipe, Filter)으로 분리합니다
ModuleDI 범위와 캡슐화 단위 — 어떤 Provider를 어디서 쓸 수 있는지 경계를 설정합니다

✍️ 1. Request Lifecycle

Nest는 HTTP 요청 하나를 아래 pipeline으로 처리합니다. Middleware~Pipe까지가 AOP layer이며, handler 본연의 비즈니스 로직과 분리된 횡단 관심사입니다.

요청
 → Middleware       ← Express chain (전역 전처리)
 → Guard            ← 진입 허가 (authorization)
 → Interceptor      ← handler 앞뒤 감싸기 (AOP 핵심)
 → Pipe             ← handler 인자 변환·검증
 → Controller       ← HTTP handler (얇게 유지)
 → (에러 시) Exception Filter
LayerInterface핵심 메서드등록
MiddlewareNestMiddleware (Class)use(req, res, next)NestModule.configure()
GuardCanActivatecanActivate()@UseGuards()
InterceptorNestInterceptorintercept() → next.handle()@UseInterceptors()
PipePipeTransformtransform()@Param() / @Body() 옆, @UsePipes()
Exception FilterExceptionFiltercatch()@UseFilters()
Controller- (@Controller())handler return@Module({ controllers })

순서가 중요한 이유

  • Guard가 false를 반환하면 Interceptor·Pipe·Controller는 실행되지 않습니다.
  • Pipe에서 exception이 throw되면 Controller method는 실행되지 않습니다.
  • Interceptor는 next.handle()로 handler를 감싸므로, handler 실행 전·후 모두 개입할 수 있습니다 (AOP의 around advice에 해당).

✍️ 2. 공통 패턴

✅ 2-1. @Injectable() — IoC 등록 메타데이터

@Injectable()은 "이 클래스를 Nest IoC 컨테이너가 관리하는 Provider로 등록한다"는 메타데이터입니다.

  • constructor로 의존성을 받을 수 있습니다 (Middleware, Guard, Service 등)
  • 다른 클래스의 constructor에 주입될 수도 있습니다
  • @Injectable()은 "다른 것을 주입한다"가 아니라 "주입받을 수 있는 Provider"를 의미합니다
// ❌ 직접 생성 — IoC 우회
const service = new CatsService();

// ✅ DI — Nest가 생성·주입
@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}
}

부트스트랩 시점

@Injectable() / providers 등록 / constructor 선언은 등록일 뿐입니다. 실제 인스턴스 생성·주입은 NestFactory.create(AppModule) 부트스트랩 시점에 일어납니다.

1. NestFactory.create(AppModule)
2. @Module 메타데이터 읽기 → 의존성 그래프 구성
3. CatsService 인스턴스 생성 (기본 Singleton)
4. CatsController 생성 → constructor에 CatsService 주입
5. 라우트 등록 완료

✅ 2-2. Class 전달 → DI 가능

@UseGuards(), @UseInterceptors(), @UseFilters() 등록 시 Class를 넘기면 Nest가 인스턴스를 생성하고 constructor 의존성을 resolve합니다.

@UseGuards(RolesGuard)           // ✅ Nest가 생성 + AuthService 등 DI
@UseGuards(new RolesGuard())     // ❌ Nest DI 컨테이너 밖에서 생성 → DI 불가

Global 등록 + DI가 필요하면 main.ts의 useGlobalX() 대신 APP_* token으로 Module에 등록합니다.

Global token대상
APP_GUARDGuard
APP_INTERCEPTORInterceptor
APP_PIPEPipe
APP_FILTERException Filter
@Module({
  providers: [
    { provide: APP_PIPE, useClass: ValidationPipe },
    { provide: APP_FILTER, useClass: HttpExceptionFilter },
  ],
})
export class AppModule {}

✅ 2-3. DTO는 class — Reflection과 DI의 연결

Pipe/ValidationPipe는 runtime에 DTO의 metatype(class 타입 정보)을 읽어 class-validator 데코레이터를 적용합니다. TypeScript interface는 컴파일 후 사라지므로 ❌, class만 ✅입니다.

이것은 Nest Decorator + Reflection 패턴의 대표적 예시입니다 — compile-time 데코레이터가 runtime 검증으로 이어집니다.


✍️ 3. First Steps

https://docs.nestjs.com/first-steps

npm i -g @nestjs/cli
nest new project-name
# 더 엄격한 TS: nest new project-name --strict
npm run start       # 일반 실행
npm run start:dev   # watch — 파일 변경 시 자동 재컴파일·재시작
npm run lint        # ESLint — await 누락, any 남용 등
npm run format      # Prettier — 들여쓰기, 줄바꿈, 따옴표

main.ts — 앱 부트스트랩

async function bootstrap() {
  const app = await NestFactory.create(AppModule); // IoC 컨테이너 초기화
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

NestFactory.create()가 Module graph를 읽고, Provider·Controller·Guard 등을 한 번에 wiring합니다.


✍️ 4. Controllers

https://docs.nestjs.com/controllers

Controller는 HTTP 요청을 받고 응답을 보내는 진입점입니다. 비즈니스 로직은 Service(Provider)에 위임하고, Controller는 얇게 유지하는 것이 DI·관심사 분리 원칙에 맞습니다.

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Get()
  findAll() {
    return this.catsService.findAll();
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.catsService.findOne(id);
  }

  @Post()
  create(@Body() dto: CreateCatDto) {
    return this.catsService.create(dto);
  }
}

@Controller('cats') + @Get() → GET /cats입니다. @Get('breed') → GET /cats/breed입니다.

✅ 4-1. Standard 방식 (권장)

값만 return하면 Nest가 응답을 처리합니다. Interceptor, Exception Filter 등 AOP layer와 정상 연동됩니다.

@Post()
@HttpCode(201)
@Header('Cache-Control', 'no-store')
create(@Body() dto: CreateCatDto) {
  return this.catsService.create(dto);
}
항목내용
bodyreturn 값입니다 (객체/배열 → JSON, primitive → 그대로)
상태 코드GET → 200, POST → 201 (기본값)
커스텀@HttpCode(), @Header(), @Redirect()
Nest 연동Interceptor, Exception Filter 등이 정상 동작합니다

쿠키 설정용 Nest decorator는 없습니다 → @Res({ passthrough: true })로 res.cookie()를 사용합니다.

✅ 4-2. Express 방식 (Library-specific)

@Res()로 Express response 객체를 직접 사용합니다. 해당 route에서 Nest Standard pipeline이 꺼집니다.

@Get()
findAll(@Res() res: Response) {
  res.status(200).json([]);   // API JSON
}

@Get('hello')
hello(@Res() res: Response) {
  res.status(200).send('Hello'); // 단순 메시지
}
항목내용
bodyres.json(), res.send() 등을 직접 호출합니다
상태 코드res.status()를 직접 설정합니다
주의return은 무시됩니다 — 반드시 res로 응답을 종료해야 하며, 그렇지 않으면 hang 상태가 됩니다
Nest 연동Interceptor, @HttpCode() 등 비활성화됩니다
단점Express에 종속되며, 테스트·이식성이 떨어집니다

✅ 4-3. 절충안 (passthrough: true)

return으로 body는 Nest에 맡기고, res로 쿠키·헤더만 직접 설정합니다. AOP pipeline을 유지하면서 Express API를 일부 사용합니다.

@Post()
login(@Res({ passthrough: true }) res: Response) {
  res.cookie('token', 'abc', { httpOnly: true });
  return { ok: true };
}
StandardExpresspassthrough
bodyreturnres.send/json()return
Nest AOP
res 직접 조작일부만 ✅

✍️ 5. Providers

https://docs.nestjs.com/providers

Provider(Service, Repository 등)는 비즈니스 로직을 담습니다. Nest DI의 핵심 단위입니다.

Controller는 HTTP 진입점, Provider는 로직, Module은 DI 범위입니다.

✅ 5-1. Service — @Injectable()

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
    this.cats.push(cat);
  }

  findAll(): Cat[] {
    return this.cats;
  }

  findOne(id: number): Cat | undefined {
    return this.cats.find((cat) => cat.id === id);
  }
}
항목내용
@Injectable()IoC 컨테이너 Provider 등록 메타데이터입니다
Scope기본 Singleton — 앱 전체에서 인스턴스 1개를 공유합니다
역할데이터 저장·조회, 비즈니스 로직을 담당합니다
nest g service cats

Node.js는 요청마다 스레드를 분리하지 않으므로, Singleton Provider는 요청 간 공유됩니다. CatsService의 cats 배열은 모든 요청이 같은 배열을 봅니다.

✅ 5-2. Constructor Injection

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}
  //          ↑ TypeScript shorthand: 선언 + this.catsService 초기화

  @Post()
  create(@Body() dto: CreateCatDto) {
    return this.catsService.create(dto);
  }
}

Nest IoC가 CatsService 타입을 보고 인스턴스를 resolve → constructor에 주입합니다. Dependency Inversion — Controller는 CatsService 구현에 직접 의존하지 않고, Nest가 연결해 줍니다.

✅ 5-3. Module providers 등록

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class AppModule {}

providers에 없으면 Nest가 CatsService를 모르므로 주입이 실패합니다.


✍️ 6. Modules

https://docs.nestjs.com/modules

Module은 Nest DI 컨테이너의 캡슐화 단위입니다. 어떤 Provider를 누가 주입할 수 있는지 경계를 정합니다.

✅ 6-1. @Module() 4가지 속성

속성역할
providersNest가 생성·관리할 Provider입니다
controllers이 Module의 Controller입니다
imports다른 Module을 가져옵니다 (export된 Provider 사용)
exports다른 Module에 공개 API로 내보낼 Provider입니다

Provider는 Module 내부에서만 주입할 수 있습니다 (캡슐화). 외부 Module에서 쓰려면 exports + imports가 필요합니다.

✅ 6-2. Feature Module

관련 Controller + Service를 기능(domain) 단위로 묶습니다 — SRP, 응집도를 높입니다.

// cats/cats.module.ts
@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

// app.module.ts
@Module({
  imports: [CatsModule],
})
export class AppModule {}
src/
  cats/
    dto/create-cat.dto.ts
    cats.controller.ts
    cats.service.ts
    cats.module.ts
  app.module.ts
  main.ts

✅ 6-3. Module 간 Provider 공유

@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],  // 다른 Module에 공개
})
export class CatsModule {}

@Module({
  imports: [CatsModule],   // export된 CatsService 사용 가능
  providers: [OrdersService],
})
export class OrdersModule {
  constructor(private catsService: CatsService) {} // 같은 Singleton
}
방식결과
exports + imports같은 Singleton 인스턴스를 공유합니다
각 Module에 providers: [CatsService]Module마다 별도 인스턴스 — 상태 불일치 위험이 있습니다
AppModule (Root)
  └── imports: [CatsModule]
        ├── controllers: [CatsController]
        ├── providers: [CatsService]
        └── exports: [CatsService]

✍️ 7. Middleware

https://docs.nestjs.com/middleware

Middleware는 Controller 실행 전 Express chain에서 동작합니다. req, res, next에 접근하며, next() 미호출 시 hang됩니다.

Middleware는 어떤 handler가 실행될지 모릅니다 — route context 밖의 전역 전처리에 적합합니다 (로깅, body parser, req.user 붙이기 등).

요청 → [Middleware] → [Guard → Interceptor → Pipe → Controller]

✅ 7-1. Functional vs Class

Functional — DI가 불필요합니다

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`${req.method} ${req.url}`);
  next();
}

Class — DI가 필요합니다 (@Injectable())

Middleware 자신이 Service를 주입받을 때 사용합니다.

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  constructor(private authService: AuthService) {}

  use(req: Request, res: Response, next: NextFunction) {
    const token = req.headers.authorization;
    if (this.authService.validate(token)) {
      req.user = this.authService.decode(token); // 이후 Guard/Controller에서 사용
      next();
    } else {
      next(); // 또는 res.status(401).send()
    }
  }
}
FunctionalClass
DI
용도로깅 등 단순 전처리AuthService 등 Service가 필요할 때 사용합니다

✅ 7-2. 등록 — NestModule + configure()

Middleware는 @Module({ providers })가 아니라 configure(consumer)로 route에 연결합니다.

  • NestModule — interface이며, configure() 구현을 강제합니다
  • MiddlewareConsumer — helper class이며, apply() / forRoutes()를 제공합니다
@Module({ imports: [CatsModule] })
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)       // Class → 요청마다 use() 실행
      .forRoutes(CatsController);    // Controller 단위 적용
      // .forRoutes('cats');         // 경로 prefix
      // .forRoutes({ path: 'cats', method: RequestMethod.GET });
  }
}

✅ 7-3. Global Middleware

// main.ts — DI 불가
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);

Class middleware + DI가 필요하면 Module에서 forRoutes('*')를 사용합니다.

MiddlewareGuard
contexthandler를 알 수 없습니다ExecutionContext로 handler를 알 수 있습니다
등록configure(consumer)@UseGuards()
역할공통 전처리route handler 실행을 허가합니다

✍️ 8. Guards

https://docs.nestjs.com/guards

Guard는 route handler 실행 허용/거부를 결정합니다 (주로 authorization).

Middleware와 달리 Guard는 ExecutionContext로 어떤 handler·decorator metadata가 붙었는지 알 수 있습니다 — route별 조건 분기가 가능합니다 (AOP + Reflection).

요청 → Middleware → Guard → Interceptor → Pipe → Controller

✅ 8-1. CanActivate 구현

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    return !!request.user; // Middleware/Guard가 미리 붙인 user
  }
}
MiddlewareGuard
반환next() 호출boolean (true/false)
용도authentication (req.user 설정)authorization (route별 권한 검사)
false 시403 Forbidden, handler가 실행되지 않습니다

✅ 8-2. @UseGuards() 등록

@Controller('cats')
@UseGuards(RolesGuard)          // Controller 전체
export class CatsController {
  @Get()
  findAll() {}                   // RolesGuard 적용

  @Post()
  @UseGuards(AuthGuard)          // method 추가 적용
  create() {}
}
@Module({
  providers: [{ provide: APP_GUARD, useClass: RolesGuard }],
})
export class AppModule {}

✅ 8-3. Metadata + Reflector — Declarative Authorization

Decorator로 metadata를 저장 → Guard에서 Reflector로 읽습니다 — handler마다 if/else 없이 선언적으로 권한을 설정합니다 (AOP + Reflection).

// roles.decorator.ts
export const Roles = Reflector.createDecorator<string[]>();

// cats.controller.ts
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {
  @Get()
  findAll() {} // @Roles 없음 → public

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

// roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get(Roles, context.getHandler());
    if (!roles) return true; // metadata 없으면 역할 검사 생략

    const { user } = context.switchToHttp().getRequest();
    return roles.some((role) => user?.roles?.includes(role));
  }
}
역할
@Roles(['admin'])handler에 필요 역할 metadata를 저장합니다
reflector.get(Roles, handler)Guard에서 metadata를 읽습니다
if (!roles) return true@Roles 없는 route = public입니다
request.userMiddleware/AuthGuard가 미리 붙인 user입니다 (관례)

✍️ 9. Interceptors

https://docs.nestjs.com/interceptors

Interceptor는 Nest AOP의 핵심 구현체입니다. handler를 next.handle()로 감싸서 실행 전·후에 로직을 끼워 넣습니다 (around advice).

요청 → ... → Interceptor ──→ next.handle() ──→ Pipe → Controller
              ↑ Before          ↑ Pointcut       ↑ handler 실행
              └──── After ← Observable stream ←──┘

✅ 9-1. intercept(context, next) — Before / After

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const req = context.switchToHttp().getRequest();
    console.log(`[Before] ${req.method} ${req.url}`);

    const now = Date.now();
    return next.handle().pipe(
      tap(() => console.log(`[After] ${Date.now() - now}ms`)),
    );
  }
}
내용
handle() 전로깅, 전처리를 수행합니다
handle()Controller method 실행 → Observable stream
.pipe(tap/map/...)stream 변환 — After 로직, 응답 mapping
handle() 미호출handler가 생략됩니다 (캐시 hit 등)

✅ 9-2. 응답 변환 · Handler 생략

Response mapping — Controller return [] → { data: [] }로 전역 통일합니다 (DRY, AOP).

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, { data: T }> {
  intercept(context: ExecutionContext, next: CallHandler) {
    return next.handle().pipe(map((data) => ({ data })));
  }
}

Standard(return) 전용입니다 — @Res() 사용 route에서는 동작하지 않습니다.

Handler 생략 (캐시)

@Injectable()
export class CacheInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler) {
    if (this.cache.has(key)) {
      return of(cachedValue); // handle() 호출 안 함 → Controller skip
    }
    return next.handle();
  }
}

✅ 9-3. 등록 · Guard vs Interceptor

@Controller('cats')
@UseInterceptors(LoggingInterceptor)
export class CatsController {}

@Module({
  providers: [{ provide: APP_INTERCEPTOR, useClass: TransformInterceptor }],
})
export class AppModule {}
GuardInterceptor
역할들어갈 수 있는지 판단합니다 (gate)들어간 뒤 무엇을 할지 감쌉니다 (wrap)
반환booleanObservable
handler허용/거부next.handle()로 감쌉니다

✍️ 10. Pipes

https://docs.nestjs.com/pipes

Pipe는 Controller method 실행 직전, handler 인자에 대해 변환·검증합니다. 시스템 경계(boundary)에서 입력을 정제하는 AOP layer입니다.

Middleware는 req 전체를 봅니다. Pipe는 @Param, @Body, @Query로 선언된 특정 인자를 알 수 있습니다 — handler signature와 연결된 검증입니다.

Pipe에서 exception이 throw되면 Controller가 실행되지 않으며, Exception Filter가 400 응답을 반환합니다.

✅ 10-1. Transformation — Parse* Pipe

URL/query/body는 항상 string입니다. Pipe가 runtime 타입 변환을 수행합니다.

@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
  return this.catsService.findOne(id);
}

// GET /cats/abc → 400
// GET /cats/1   → id: 1 (number)
Pipe역할
ParseIntPipestring → number로 변환합니다
ParseFloatPipestring → float로 변환합니다
ParseBoolPipestring → boolean으로 변환합니다
ParseUUIDPipeUUID를 검증합니다
ParseEnumPipeenum을 검증합니다
DefaultValuePipe값이 없을 때 기본값을 적용합니다
@Get()
findAll(
  @Query('activeOnly', new DefaultValuePipe(false), ParseBoolPipe) activeOnly: boolean,
  @Query('page', new DefaultValuePipe(0), ParseIntPipe) page: number,
) {
  return this.catsService.findAll({ activeOnly, page });
}

✅ 10-2. Validation — ValidationPipe + DTO class

Declarative validation — DTO class에 @IsString() 등 decorator를 붙이면 ValidationPipe가 runtime에 검증합니다.

export class CreateCatDto {
  @IsString()
  name: string;

  @IsInt()
  @Min(0)
  age: number;

  @IsString()
  breed: string;
}

@Post()
create(@Body() dto: CreateCatDto) {
  return this.catsService.create(dto);
}

Global ValidationPipe

app.useGlobalPipes(new ValidationPipe({
  whitelist: true,
  forbidNonWhitelisted: true,
  transform: true,
}));

@Module({
  providers: [{ provide: APP_PIPE, useClass: ValidationPipe }],
})
export class AppModule {}

✅ 10-3. PipeTransform · Middleware vs Pipe

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    // metadata.type: 'body' | 'query' | 'param' | 'custom'
    // metadata.metatype: CreateCatDto (class) — interface면 undefined
    return value;
  }
}
MiddlewarePipe
대상req, res (전체)handler 인자 (특정 param)
contexthandler/param을 알 수 없습니다@Body, @Param을 알 수 있습니다
AOP 관점chain-level 전처리boundary validation/transformation

✍️ 11. Exception Filters

https://docs.nestjs.com/exception-filters

Exception Filter는 에러 응답을 처리하는 AOP layer입니다. Pipe·Guard·Controller 어디서든 throw된 exception을 잡아 일관된 JSON으로 변환합니다.

Exception응답
HttpException subclass{ statusCode, message, ... }
unrecognized{ statusCode: 500, message: "Internal server error" }

✅ 11-1. throw — Built-in Exceptions

@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
  const cat = this.catsService.findOne(id);
  if (!cat) {
    throw new NotFoundException(`Cat #${id} not found`);
  }
  return cat;
}
throw new BadRequestException('Invalid input');
throw new UnauthorizedException();
throw new ForbiddenException();

Custom exception은 HttpException을 extends하면 built-in filter가 자동 처리합니다.

export class CatNotFoundException extends HttpException {
  constructor(id: number) {
    super(`Cat #${id} not found`, HttpStatus.NOT_FOUND);
  }
}

✅ 11-2. Custom Exception Filter

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response.status(status).json({
      statusCode: status,
      message: exception.message,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}
내용
@Catch(HttpException)잡을 exception 타입입니다 (비우면 all)
ArgumentsHostHTTP/Microservice/WS 등 context를 추상화합니다
host.switchToHttp()Express req, res를 반환합니다

✅ 11-3. 등록

@Post()
@UseFilters(HttpExceptionFilter)
create() {
  throw new ForbiddenException();
}

@Module({
  providers: [{ provide: APP_FILTER, useClass: HttpExceptionFilter }],
})
export class AppModule {}
Built-in global filterCustom filter
설정자동 적용@UseFilters() / APP_FILTER로 등록
용도기본 JSON 에러 응답timestamp, path, 로깅, 커스텀 schema 추가

✍️ 12. Custom Decorators

https://docs.nestjs.com/custom-decorators

Nest는 Decorator + Metadata + Reflection으로 declarative programming을 구현합니다. @Body(), @Roles(), @User() 모두 같은 패턴입니다.

✅ 12-1. Built-in Param Decorators

DecoratorExpress
@Req()req
@Res()res
@Param('id')req.params.id
@Body()req.body
@Query('age')req.query.age
@Headers('authorization')req.headers.authorization
@Ip()req.ip

✅ 12-2. @User() — Custom Param Decorator

export const User = createParamDecorator(
  (data: string | undefined, ctx: ExecutionContext) => {
    const user = ctx.switchToHttp().getRequest().user;
    return data ? user?.[data] : user;
  },
);

@Get('profile')
getProfile(@User() user: UserEntity) {
  return user;
}

@Get('greeting')
greet(@User('firstName') firstName: string) {
  return `Hello ${firstName}`;
}

✅ 12-3. Pipe 연동 · applyDecorators() Composition

Custom decorator에도 Pipe를 적용할 수 있습니다 — validateCustomDecorators: true가 필요합니다.

@Get('profile')
getProfile(
  @User(new ValidationPipe({ validateCustomDecorators: true }))
  user: UserEntity,
) {
  return user;
}

여러 decorator를 하나로 합칩니다 (DRY).

export function Auth(...roles: string[]) {
  return applyDecorators(
    SetMetadata('roles', roles),
    UseGuards(AuthGuard, RolesGuard),
  );
}

@Get('users')
@Auth('admin')
findAllUsers() {}

✍️ 13. 개념 정리

패턴Nest 구현
IoC / DI@Injectable() + providers + constructor injection + NestFactory.create()로 구현합니다
Module 캡슐화imports / exports로 Provider 가시성·Singleton 공유를 관리합니다
AOPGuard (before gate) → Interceptor (around) → Pipe (before args) → Filter (after error)로 구현합니다
Decorator + Metadata@Roles(), @Body(), DTO @IsString() — 선언적으로 설정합니다
ReflectionReflector.get(), ValidationPipe metatype — runtime에 metadata를 소비합니다
관심사 분리Controller (HTTP) / Service (logic) / Pipe (validation) / Filter (error)로 분리합니다
Declarative > Imperativehandler마다 if/else 대신 decorator + pipeline layer에 위임합니다
profile
Write a little every day, without hope, without despair ✍️

0개의 댓글