본문 바로가기

NestJS 프레임워크

NestJS의 모듈·컨트롤러·프로바이더와 의존성 주입 구조를 익히고 파이프·가드로 요청 검증과 접근을 제어합니다.

이전 절에서 Node.js와 Express를 사용하여 기본적인 백엔드 애플리케이션을 구축하는 방법을 살펴보았습니다.

Express는 유연하고 최소한의 기능을 제공하지만, 대규모 애플리케이션을 개발할 때는 구조화된 아키텍처와 더 많은 기능을 제공하는 프레임워크의 필요성을 느끼게 됩니다.

이때 등장하는 것이 바로 NestJS입니다.

NestJS는 Node.js 환경에서 효율적이고 확장 가능한 서버 측 애플리케이션을 구축하기 위한 진보적인 Node.js 프레임워크입니다.

타입스크립트를 기본 언어로 사용하며, Angular에서 영감을 받은 모듈식 아키텍처를 채택하여 엔터프라이즈급 애플리케이션 개발에 특히 적합합니다.

그림은 feature module과 repository token까지 분리한 확장 구조입니다. 아래 AppModule 예제는 AppController와 AppService만 등록하며 export된 repository 경계는 아직 구현하지 않습니다.

NEST MODULE · DI VISIBILITY

Nest module은 provider token의 가시성과 조립 경계를 정한다

확장 설계: 원문 AppModule은 AppController와 AppService만 등록합니다. 아래 feature module·repository token은 추가 구조입니다. 다른 module에는 imports와 exports metadata로 공개한 provider token이 보이며, TypeScript import 자체는 DI 가시성을 만들지 않습니다.

bootstrap, AppModule, feature module, controller, provider, repository와 exported token의 Nest DI 구조 bootstrap이 AppModule을 생성하고 AppModule이 FeatureModule을 imports에 둔다. FeatureModule은 controller와 provider를 등록하며 provider가 repository token을 주입받는다. 다른 module에는 exports에 포함된 token만 보인다. 생성 controllers 등록 inject token 공개 BOOTSTRAP NestFactory root 하나 ROOT MODULE AppModule imports FEATURE MODULE ProjectModule 등록 · 경계 Project Controller HTTP adapter Project Service use case PROJECT_ REPO repository port EXPORT 공개 token consumer module
  1. root bootstrap

    NestFactory는 하나의 root module을 기준으로 전체 DI graph를 조립합니다.

  2. feature 등록

    feature module이 controller와 provider를 metadata에 등록해 같은 module 안의 가시성을 만듭니다.

  3. token 주입

    provider는 concrete class 또는 명시 token을 통해 repository port를 주입받습니다.

  4. export 경계

    다른 module은 feature module을 import해도 exports에 공개된 token만 주입할 수 있습니다.

파일 import는 JavaScript 모듈 연결이고 Nest module metadata는 런타임 DI 연결이다. 두 그래프를 같은 것으로 취급하면 provider 해석 실패가 생긴다.


NestJS 소개 및 특징

NestJS는 다음과 같은 주요 특징을 가집니다.

  • 타입스크립트 기본 지원: NestJS는 타입스크립트를 1급 시민(first-class citizen)으로 지원하여, 강력한 타입 안전성과 향상된 개발 경험을 제공합니다. 이는 대규모 프로젝트에서 코드의 유지보수성과 가독성을 크게 높입니다.
  • 모듈식 아키텍처: Angular와 유사하게 모듈(Module), 컨트롤러(Controller), 프로바이더(Provider, 서비스/리포지토리 등) 개념을 사용하여 애플리케이션을 구조화합니다. 이는 코드의 응집도를 높이고, 재사용성을 촉진하며, 테스트를 용이하게 합니다.
  • 의존성 주입 (Dependency Injection, DI): NestJS는 강력한 DI 컨테이너를 내장하고 있어, 컴포넌트 간의 결합도를 낮추고 테스트 가능한 코드를 작성할 수 있도록 돕습니다.
  • 데코레이터 기반: @Controller(), @Get(), @Injectable() 등 데코레이터를 광범위하게 사용하여 메타데이터를 추가하고, 코드를 더 선언적이고 간결하게 만듭니다.
  • Express 기반: 내부적으로 Express.js를 사용하며, 필요에 따라 Fastify와 같은 다른 HTTP 프레임워크로 쉽게 전환할 수 있습니다. Express의 미들웨어와 기능을 그대로 활용할 수 있습니다.
  • CLI (Command Line Interface): 강력한 CLI 도구를 제공하여 프로젝트 생성, 모듈/컨트롤러/서비스 생성 등 반복적인 작업을 자동화하여 개발 생산성을 높입니다.
  • 문서화: 매우 잘 정리된 공식 문서를 제공하여 학습과 문제 해결을 돕습니다.

NestJS 프로젝트 시작하기

NestJS 프로젝트를 시작하는 가장 쉬운 방법은 Nest CLI를 사용하는 것입니다.

Nest CLI 설치
npm install -g @nestjs/cli
새 프로젝트 생성
nest new my-nestjs-app
cd my-nestjs-app

이 명령어는 기본 NestJS 프로젝트 구조와 필요한 모든 의존성을 설치합니다.

개발 서버 실행
npm run start:dev

서버가 http://localhost:3000에서 실행됩니다.

기본적으로 / 경로로 접속하면 Hello World!를 반환합니다.


NestJS의 핵심 구성 요소와 타입스크립트

NestJS는 모듈, 컨트롤러, 프로바이더(서비스)의 세 가지 핵심 구성 요소로 애플리케이션을 구조화합니다.

모듈 (Modules)

모듈은 애플리케이션의 구조를 조직화하는 기본 단위입니다.

관련 컨트롤러와 프로바이더를 그룹화합니다.

모든 NestJS 애플리케이션은 최소한 하나의 루트 모듈(일반적으로 AppModule)을 가집니다.

src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],       // 이 모듈에서 사용할 다른 모듈들을 임포트합니다.
  controllers: [AppController], // 이 모듈에 속한 컨트롤러들을 정의합니다.
  providers: [AppService],   // 이 모듈에 속한 프로바이더(서비스, 리포지토리 등)들을 정의합니다.
})
export class AppModule {}

@Module() 데코레이터는 클래스를 NestJS 모듈로 선언합니다.

컨트롤러 (Controllers)

컨트롤러는 들어오는 요청(Request)을 처리하고 응답(Response)을 반환하는 역할을 합니다.

특정 경로(Route)에 대한 요청을 담당하며, 요청의 유효성을 검사하고, 서비스 계층으로 작업을 위임합니다.

src/app.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { AppService } from './app.service';

// DTO (Data Transfer Object) 정의: 요청 본문의 타입을 명시합니다.
interface CreateUserDto {
  name: string;
  email: string;
}

@Controller('users') // '/users' 경로에 대한 요청을 처리합니다.
export class AppController {
  constructor(private readonly appService: AppService) {} // 의존성 주입 (AppService)

  @Get() // GET /users
  getHello(): string {
    return this.appService.getHello();
  }

  @Get(':id') // GET /users/:id
  // @Param() 데코레이터는 URL 파라미터를 가져오고, 타입스크립트가 타입을 추론합니다.
  getUserById(@Param('id') id: string): string {
    return `User ID: ${id}`;
  }

  @Post() // POST /users
  // @Body() 데코레이터는 요청 본문을 가져오고, CreateUserDto 타입을 명시하여 타입 안정성을 확보합니다.
  createUser(@Body() createUserDto: CreateUserDto): string {
    console.log('Received new user:', createUserDto);
    return `User ${createUserDto.name} (${createUserDto.email}) created!`;
  }
}

@Controller(), @Get(), @Post(), @Body(), @Param() 등 다양한 데코레이터와 타입스크립트의 인터페이스를 활용하여 라우팅과 요청 본문의 타입을 명확하게 정의할 수 있습니다.

프로바이더 (Providers)

프로바이더는 NestJS의 핵심 개념 중 하나입니다.

서비스(Service), 리포지토리(Repository), 팩토리(Factory) 등 애플리케이션의 비즈니스 로직을 포함하는 모든 클래스는 프로바이더가 될 수 있습니다.

프로바이더는 의존성 주입 시스템을 통해 다른 컴포넌트(컨트롤러, 다른 서비스 등)에 주입될 수 있습니다.

src/app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable() // 이 클래스가 NestJS의 의존성 주입 시스템에 의해 관리될 수 있음을 나타냅니다.
export class AppService {
  getHello(): string {
    return 'Hello World from AppService!';
  }

  // 예시: 사용자 데이터 처리 로직 (실제로는 데이터베이스 연동)
  createUser(name: string, email: string): { id: number; name: string; email: string } {
    const newUser = { id: Math.floor(Math.random() * 1000), name, email };
    console.log('User created in service:', newUser);
    return newUser;
  }
}

@Injectable() 데코레이터는 NestJS가 이 클래스의 인스턴스를 생성하고 필요할 때 주입할 수 있도록 합니다.


NestJS의 고급 기능과 타입스크립트

NestJS는 엔터프라이즈급 애플리케이션 개발을 위한 다양한 고급 기능을 제공하며, 이들 역시 타입스크립트와 잘 통합됩니다.

파이프 (Pipes)

파이프는 들어오는 요청 데이터를 변환(transform)하거나 유효성 검사(validate)하는 데 사용됩니다.

컨트롤러 핸들러가 실행되기 전에 데이터를 처리합니다.

src/pipes/parse-int.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed: Parameter is not an integer.');
    }
    return val;
  }
}
src/app.controller.ts (사용 예시)
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
// ...

@Controller('users')
export class AppController {
  // ...
  @Get(':id')
  // @Param('id', ParseIntPipe)를 사용하여 id 파라미터가 숫자로 변환되고 유효성 검사됩니다.
  getUserById(@Param('id', ParseIntPipe) id: number): string {
    // 이제 id는 number 타입임을 보장할 수 있습니다.
    return `User ID: ${id}`;
  }
}

위 직접 만든 ParseIntPipe는 parseInt 결과만 확인하므로 "12abc"도 12로 받아들입니다. 다음 사용 블록이 가져오는 것은 @nestjs/common의 내장 ParseIntPipe이며 앞의 사용자 정의 클래스와 다른 구현입니다.

PipeTransform<T, R> 인터페이스를 구현하여 파이프의 입력(T)과 출력(R) 타입을 명확히 정의할 수 있습니다.

가드 (Guards)

가드는 특정 라우트 핸들러가 실행되기 전에 권한 부여(authorization) 로직을 처리하는 데 사용됩니다.

src/guards/auth.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    // 실제로는 JWT 토큰 검증, 세션 확인 등의 로직이 들어갑니다.
    const hasAuthHeader = request.headers.authorization ? true : false;
    console.log('AuthGuard is running. Has Auth Header:', hasAuthHeader);
    return hasAuthHeader; // 인증 성공 시 true, 실패 시 false 반환
  }
}
src/app.controller.ts (사용 예시)
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './guards/auth.guard';
// ...

@Controller('admin')
@UseGuards(AuthGuard) // 이 컨트롤러의 모든 라우트에 AuthGuard 적용
export class AdminController {
  @Get('dashboard') // GET /admin/dashboard
  getDashboard(): string {
    return 'Welcome to the Admin Dashboard!';
  }
}

이 AuthGuard는 헤더 존재 여부만 검사하므로 토큰의 유효성이나 관리자 권한을 검증하지 않습니다. CreateUserDto 인터페이스도 런타임 본문 검증을 추가하지 않으며, 컨트롤러의 POST는 서비스 저장을 호출하지 않고 문자열만 반환합니다.

CanActivate 인터페이스를 구현하여 가드의 동작을 정의하고, @UseGuards() 데코레이터를 사용하여 컨트롤러나 특정 라우트에 적용할 수 있습니다.


NestJS와 타입스크립트의 시너지

NestJS는 타입스크립트를 기본으로 채택함으로써 다음과 같은 강력한 이점을 제공합니다.

  • 설계 일관성: 타입스크립트의 인터페이스, 클래스, 데코레이터 등을 활용하여 백엔드 애플리케이션의 아키텍처를 일관되고 예측 가능하게 설계할 수 있습니다.
  • 컴파일 시점 오류 감지: 런타임에 발생할 수 있는 타입 관련 오류를 개발 단계에서 미리 잡아내어 디버깅 시간을 단축하고 코드의 안정성을 높입니다.
  • 강력한 IDE 지원: 타입 정보 덕분에 자동 완성, 코드 탐색, 리팩터링 기능이 매우 강력해져 개발 생산성이 크게 향상됩니다.
  • 문서화 효과: 타입 정의 자체가 코드의 의도와 데이터 구조를 명확하게 문서화하는 역할을 하여, 팀원 간의 협업을 원활하게 합니다.
  • 확장성 및 유지보수성: 모듈, 컨트롤러, 프로바이더로 구분된 명확한 구조와 의존성 주입은 애플리케이션의 확장과 장기적인 유지보수를 용이하게 합니다.

NestJS에서는 클래스와 데코레이터가 많아질수록 요청 흐름의 책임 분리가 더 중요해집니다.


NestJS 구조 요약

NestJS는 Node.js 환경에서 모듈, DI, 데코레이터를 기반으로 서버 애플리케이션 구조를 구성하는 프레임워크입니다.

타입스크립트를 기본 언어로 사용하고, 모듈식 아키텍처, 의존성 주입, 데코레이터 기반 문법을 제공해 개발 생산성과 코드 품질을 동시에 높입니다.

Express 같은 경량 프레임워크가 소규모 프로젝트에 적합하다면, NestJS는 대규모 엔터프라이즈급 애플리케이션에서 복잡한 백엔드 로직을 구조적으로 관리하기에 더 유리합니다.

NestJS 요청 흐름은 데코레이터가 많아 보이지만, 실제로는 모듈 경계와 요청 체인의 책임을 순서대로 나누는 구조입니다.

아래 그림의 interceptor와 exception filter는 일반 요청 생명주기를 보여 줍니다. 위 예제에 이 확장 지점이 모두 등록되어 있는 것은 아닙니다.

Nest 요청은 성공과 실패가 서로 다른 되감기 경로를 가진다 — 상태 추적

요청이 guard/pipe/controller와 interceptor 반환 또는 exception filter로 이동하며 현재 실행 위치·허용 여부가 달라진다. 등록되지 않은 hook은 확장 모델임을 명시한다.

NEST REQUEST · EXECUTION ORDER

Nest 요청은 성공과 실패가 서로 다른 되감기 경로를 가진다

일반 생명주기: 원문에 interceptor와 filter가 모두 등록된 것은 아닙니다. 성공은 interceptor를 역순으로 돌아오며, pipe·handler·service 오류는 catchError로 처리할 수 있습니다. 미처리 예외만 가장 가까운 matching filter가 응답으로 바꿉니다.

Nest middleware, guard, interceptor, pipe, controller, provider와 exception filter의 요청 실행 시퀀스 클라이언트 요청이 middleware와 guard를 통과하고 interceptor pre와 pipe를 거쳐 controller와 provider로 들어간다. 성공 결과는 interceptor post를 거쳐 응답한다. 예외는 적용 가능한 exception filter가 응답으로 번역한다. HTTP request next() canActivate → pre pipe transform → handler provider call domain result handler result post transform → response catchError 가능미처리 예외Filterclosest matchingsafe error response EDGE Client PRE-ROUTE Middleware ACCESS Guard AROUND Interceptor Pipe + Controller Provider 도메인 작업
정상 요청의 바깥/안쪽 interceptor 왕복과 실패 시 가장 가까운 matching filter 경로를 분리한다.mw→guard: middleware 다음 guard。guard→pre: 정상 처리 진입。guard→reject: 처리되지 않은 예외。pre→handler: pipe 통과 후 handler/provider。handler→post: interceptor 역순 반환。handler→filter: catchError 등에서 처리되지 않은 오류。reject→filter: matching filter 하나가 처리。post→response: 정상 응답。filter→response: 필터가 오류 응답 작성허용거부성공Client → MiddlewareClient → MiddlewareGuard 앞에서 처리Guard 허용?Guard 허용?요청 진행 판단인터셉터 전처리인터셉터전처리Pipe →Controller거부·미처리 오류거부 ·미처리오류해당filter탐색Provider 처리Provider처리응답또는예외가장 가까운 Filter가장가까운Filterroute >controller> global인터셉터 후처리인터셉터후처리진입의역순반환Client 응답Client 응답정상 값 또는 오류 응답
단계별 조건과 보충 설명
  1. 진입 경로

    요청은 middleware에서 시작해 guard의 접근 판정과 interceptor의 사전 구간을 통과합니다.

  2. 인자와 handler

    pipe가 route 인자를 변환·검증한 뒤 controller가 provider의 use case를 호출합니다.

  3. 성공 되감기

    provider 결과는 controller에서 interceptor post 구간으로 돌아가 변환된 응답이 됩니다.

  4. 실패 되감기

    pipe·controller·service의 오류를 interceptor의 catchError가 처리할 수 있습니다. 미처리 예외는 route → controller → global 순서에서 가장 가까운 matching filter 하나가 처리합니다.

그림의 아래 오류 분기는 handler 내부 오류의 예입니다. guard·pipe 등 다른 위치의 미처리 예외도 예외 계층으로 이동하며, middleware 예외에는 global filter만 적용됩니다. filter끼리 같은 예외를 다시 전달하지 않습니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
  • 원래 본문의 구현과 도표가 설명한 설계 보강을 분리한다. 서사에 포함한 확장 동작은 assumptions에 명시하고 이미 구현된 안전장치로 주장하지 않는다.
관계도의 보충 설명
원도표의 전체 관계·조건·설명
구분내용
전체 관계·조건 설명클라이언트 요청이 middleware와 guard를 통과하고 interceptor pre와 pipe를 거쳐 controller와 provider로 들어간다. 성공 결과는 interceptor post를 거쳐 응답한다. 예외는 적용 가능한 exception filter가 응답으로 변환한다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
진입과 guard실행: 진입/guard / 거절: 업무 실행 안 함middleware 뒤 guard가 요청 허용 여부를 판정한다guard는 pipe 앞이며 각 hook은 등록된 확장 모델을 전제한다
업무 실행입력: 검증/변환 값 / 현재: controller/providerinterceptor pre와 pipe 뒤 controller/provider를 실행한다허용 경로만 실제 업무에 도달한다
반환 또는 오류성공: 응답 변환 / 오류: 적용 filter성공은 interceptor post, 처리되지 않은 오류는 가장 가까운 적용 filter로 간다filter와 성공 post를 모든 요청의 연속 단계로 그리지 않는다

Nest 요청은 성공과 실패가 서로 다른 되감기 경로를 가진다 — 상태 추적

요청이 guard/pipe/controller와 interceptor 반환 또는 exception filter로 이동하며 현재 실행 위치·허용 여부가 달라진다. 등록되지 않은 hook은 확장 모델임을 명시한다.

NEST REQUEST · EXECUTION ORDER

Nest 요청은 성공과 실패가 서로 다른 되감기 경로를 가진다

일반 생명주기: 원문에 interceptor와 filter가 모두 등록된 것은 아닙니다. 성공은 interceptor를 역순으로 돌아오며, pipe·handler·service 오류는 catchError로 처리할 수 있습니다. 미처리 예외만 가장 가까운 matching filter가 응답으로 바꿉니다.

Nest middleware, guard, interceptor, pipe, controller, provider와 exception filter의 요청 실행 시퀀스 클라이언트 요청이 middleware와 guard를 통과하고 interceptor pre와 pipe를 거쳐 controller와 provider로 들어간다. 성공 결과는 interceptor post를 거쳐 응답한다. 예외는 적용 가능한 exception filter가 응답으로 번역한다. HTTP request next() canActivate → pre pipe transform → handler provider call domain result handler result post transform → response catchError 가능미처리 예외Filterclosest matchingsafe error response EDGE Client PRE-ROUTE Middleware ACCESS Guard AROUND Interceptor Pipe + Controller Provider 도메인 작업
정상 요청의 바깥/안쪽 interceptor 왕복과 실패 시 가장 가까운 matching filter 경로를 분리한다.mw→guard: middleware 다음 guard。guard→pre: 정상 처리 진입。guard→reject: 처리되지 않은 예외。pre→handler: pipe 통과 후 handler/provider。handler→post: interceptor 역순 반환。handler→filter: catchError 등에서 처리되지 않은 오류。reject→filter: matching filter 하나가 처리。post→response: 정상 응답。filter→response: 필터가 오류 응답 작성허용거부성공Client → MiddlewareClient → MiddlewareGuard 앞에서 처리Guard 허용?Guard 허용?요청 진행 판단인터셉터 전처리인터셉터전처리Pipe →Controller거부·미처리 오류거부 ·미처리오류해당filter탐색Provider 처리Provider처리응답또는예외가장 가까운 Filter가장가까운Filterroute >controller> global인터셉터 후처리인터셉터후처리진입의역순반환Client 응답Client 응답정상 값 또는 오류 응답
단계별 조건과 보충 설명
  1. 진입 경로

    요청은 middleware에서 시작해 guard의 접근 판정과 interceptor의 사전 구간을 통과합니다.

  2. 인자와 handler

    pipe가 route 인자를 변환·검증한 뒤 controller가 provider의 use case를 호출합니다.

  3. 성공 되감기

    provider 결과는 controller에서 interceptor post 구간으로 돌아가 변환된 응답이 됩니다.

  4. 실패 되감기

    pipe·controller·service의 오류를 interceptor의 catchError가 처리할 수 있습니다. 미처리 예외는 route → controller → global 순서에서 가장 가까운 matching filter 하나가 처리합니다.

그림의 아래 오류 분기는 handler 내부 오류의 예입니다. guard·pipe 등 다른 위치의 미처리 예외도 예외 계층으로 이동하며, middleware 예외에는 global filter만 적용됩니다. filter끼리 같은 예외를 다시 전달하지 않습니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
  • 원래 본문의 구현과 도표가 설명한 설계 보강을 분리한다. 서사에 포함한 확장 동작은 assumptions에 명시하고 이미 구현된 안전장치로 주장하지 않는다.

관계도의 보충 설명

원도표의 전체 관계·조건·설명
구분내용
전체 관계·조건 설명클라이언트 요청이 middleware와 guard를 통과하고 interceptor pre와 pipe를 거쳐 controller와 provider로 들어간다. 성공 결과는 interceptor post를 거쳐 응답한다. 예외는 적용 가능한 exception filter가 응답으로 변환한다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
진입과 guard실행: 진입/guard / 거절: 업무 실행 안 함middleware 뒤 guard가 요청 허용 여부를 판정한다guard는 pipe 앞이며 각 hook은 등록된 확장 모델을 전제한다
업무 실행입력: 검증/변환 값 / 현재: controller/providerinterceptor pre와 pipe 뒤 controller/provider를 실행한다허용 경로만 실제 업무에 도달한다
반환 또는 오류성공: 응답 변환 / 오류: 적용 filter성공은 interceptor post, 처리되지 않은 오류는 가장 가까운 적용 filter로 간다filter와 성공 post를 모든 요청의 연속 단계로 그리지 않는다

데코레이터, 요청 값, 업무 호출을 나누어 확인하기

CLI가 만든 초기 예제의 GET /와 이후 @Controller('users')로 바꾼 컨트롤러의 경로는 다릅니다. 컨트롤러 블록은 같은 파일의 단계별 교체 예제입니다. 같은 이름의 클래스를 모두 이어 붙이지 않습니다. 또한 클래스를 TypeScript로 import하는 것과 Nest 모듈의 providers·imports·exports에 연결하는 것은 서로 다른 작업입니다. Nest의 provider 공개 규칙은 다른 모듈에서 주입할 provider를 공개하도록 설명합니다.

원문 POST에 { name: 12 }가 들어오면 createUser()는 User 12 (undefined) created!라는 문자열을 만듭니다. CreateUserDto가 인터페이스라서 값 검사 코드가 생기지 않고, 이 메서드는 appService.createUser()도 호출하지 않습니다. 서비스의 생성 예제를 작성한 것과 HTTP 요청이 그 메서드를 실행하는 것은 별도로 확인해야 합니다. Nest의 파이프 설명처럼 인터페이스는 실행 코드에서 사라지므로 실제 검증 스키마나 검증 메타데이터가 있는 클래스와 파이프를 연결해야 합니다.

파이프 이름이 같아도 구현을 확인합니다. 직접 만든 파이프는 "12abc"를 12로 바꾸지만, 사용 블록에서 import한 내장 파이프는 전체 정수 문자열을 검사하여 이 값을 거절합니다. 다만 정수 변환은 양수 ID 정책이나 Number.isSafeInteger 검사를 대신하지 않습니다. 현재 내장 ParseIntPipe 구현에는 음수 정수 문자열을 허용하는 검사와 parseInt 변환이 있습니다.

연습: 등록한 기능이 실제로 실행되는 조건

  1. 사용자 정의 파이프에 "12abc"·"abc"·"-2"를 넣으면 각각 어떻게 되는가? 내장 파이프로 "-2"를 변환하면 존재하는 사용자까지 보장되는가?
  2. 위 POST 컨트롤러에 { name: 12 }를 주었을 때 반환 문자열과 서비스 생성 메서드 호출 횟수를 구하라. 서비스의 Math.random()이 두 번 모두 0이라면 두 생성 결과의 ID는 무엇이며 유일성이 보장되는가?
  3. 관리자 가드에 헤더가 없는 요청과 authorization: "Bearer invalid"인 요청을 넣었다. 반환값과 실제로 확인하지 않은 조건을 설명하라. 다른 모듈에서 repository 클래스를 import만 하면 그림의 주입 경계가 연결되는가?
풀이: 변환 성공, 접근 허용, 저장 완료를 구별하기
  1. 사용자 정의 파이프는 12, BadRequestException, -2를 각각 만든다. 내장 파이프도 "-2"를 정수로 변환하지만 양수 여부나 DB 존재 여부를 검사하지 않는다. 따라서 ID 정책과 조회·404 분기는 별도로 필요하다.
  2. 문자열은 User 12 (undefined) created!, 생성 메서드 호출 횟수는 0이다. 서비스를 따로 두 번 호출하고 난수가 모두 0이라면 두 ID 모두 0이다. 이 교육용 계산에는 유일성 제약이나 저장소가 없으므로 충돌을 막는 저장 정책이 추가로 필요하다.
  3. 헤더가 없으면 false, 비어 있지 않은 잘못된 토큰 문자열은 true다. 현재 코드는 서명·만료·사용자·관리자 역할을 검사하지 않는다. 가드가 false를 반환하면 기본 Nest 요청 처리에서는 403이 되지만, 이 가드 자체가 토큰 검증을 수행했다는 뜻은 아니다. 파일 import만으로 다른 모듈의 주입이 공개되지 않으며 선언 모듈의 등록·export와 소비 모듈의 import를 맞춰야 한다. 공식 가드 문서에서도 false의 기본 예외 처리를 설명한다.