NestJS 동작 방식 및 아키텍처 설명

기본 동작 흐름

NestJS의 기본 흐름은 다음과 같습니다:

  1. main.ts: 애플리케이션의 시작점입니다. 여기서 NestFactory.create()를 사용하여 NestJS 애플리케이션을 부트스트랩합니다.
  2. app.module.ts: 루트 모듈로, 애플리케이션의 진입점입니다. 여기서 다른 모든 모듈과 컴포넌트를 가져옵니다.
  3. Feature Modules: 애플리케이션의 각 기능별로 구분된 모듈(UserModule, ProductModule 등)입니다.
  4. Controllers: HTTP 요청을 처리하고 클라이언트에 응답을 반환합니다.
  5. Services: 비즈니스 로직을 포함하며, 데이터베이스와 상호작용합니다.

주요 구성 요소 설명

1. Entity (엔티티)

  • 데이터베이스 테이블 구조를 정의하는 클래스입니다.
  • TypeORM이나 Mongoose와 함께 사용할 때 @Entity() 또는 @Schema() 데코레이터로 정의됩니다.
  • 데이터 모델의 속성, 관계, 제약조건 등을 정의합니다.

2. Guard (가드)

  • 특정 라우트의 접근 권한을 결정합니다.
  • 주로 인증 및 권한 부여에 사용됩니다.
  • 요청이 처리되기 전에 실행되어 true(진행) 또는 false(거부) 반환합니다.
  • 예: @UseGuards(AuthGuard()) 데코레이터로 보호된 라우트 설정

3. Middleware (미들웨어)

  • 요청이 컨트롤러에 도달하기 전에 실행되는 함수입니다.
  • Express 미들웨어와 유사하게 작동합니다.
  • 요청 로깅, 헤더 수정, 본문 파싱 등의 작업을 수행합니다.
  • 전역 또는 특정 라우트에 적용할 수 있습니다.

4. Pipe (파이프)

  • 입력 데이터 변환 및 유효성 검사를 수행합니다.
  • 컨트롤러 메서드가 처리하기 전에 데이터를 변환하거나 검증합니다.
  • 내장 파이프: ValidationPipe, ParseIntPipe 등
  • 예: @Body(ValidationPipe) createUserDto: CreateUserDto

5. Filter (필터)

  • 예외 처리 메커니즘을 제공합니다.
  • 애플리케이션에서 발생한 예외를 잡아 적절한 응답으로 변환합니다.
  • 전역 또는 컨트롤러/메서드 단위로 적용 가능합니다.
  • 예: @UseFilters(HttpExceptionFilter)

6. Interceptor (인터셉터)

  • 요청-응답 주기에 추가 로직을 바인딩합니다.
  • 메서드 실행 전후에 로직을 추가할 수 있습니다.
  • 응답 변환, 캐싱, 로깅, 오류 처리 등에 사용됩니다.
  • RxJS Observable을 활용한 스트림 조작이 가능합니다.

HTTP 요청 처리 흐름

HTTP 요청이 들어오면 다음 순서로 처리됩니다:

  1. Middleware → 2. Guards → 3. Interceptors(전) → 4. Pipes → 5. Controller → 6. Service → 7. Interceptors(후) → 8. 응답

데이터베이스 통합

Mongoose 통합

// app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
 
@Module({
  imports: [
    MongooseModule.forRoot('mongodb://localhost/nest'),
    // 특정 기능 모듈...
  ],
})
export class AppModule {}
 
// user.module.ts
@Module({
  imports: [
    MongooseModule.forFeature([{ name: 'User', schema: UserSchema }]),
  ],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

Mongoose를 사용할 때:

  • 스키마 정의는 @Schema() 데코레이터를 사용합니다.
  • 모델 인젝션은 @InjectModel() 데코레이터로 합니다.
  • MongoDB의 스키마 유연성을 활용할 수 있습니다.

TypeORM 통합

// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
 
@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'test',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
    }),
    // 특정 기능 모듈...
  ],
})
export class AppModule {}
 
// user.module.ts
@Module({
  imports: [
    TypeOrmModule.forFeature([User]),
  ],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

TypeORM을 사용할 때:

  • 엔티티 정의는 @Entity() 데코레이터를 사용합니다.
  • 관계 맵핑(OneToMany, ManyToOne 등)을 쉽게 정의할 수 있습니다.
  • SQL 기반 데이터베이스에 최적화되어 있습니다.

주요 차이점

  1. Mongoose vs TypeORM:
    • Mongoose는 MongoDB용으로, 스키마가 유연하고 NoSQL에 최적화되어 있습니다.
    • TypeORM은 관계형 데이터베이스에 강점이 있고, 다양한 DB 벤더(MySQL, PostgreSQL 등)를 지원합니다.
  2. 모델 정의 방식:
    • Mongoose: @Schema() 데코레이터와 SchemaFactory를 사용합니다.
    • TypeORM: @Entity() 데코레이터와 @Column(), @PrimaryGeneratedColumn() 등을 사용합니다.
  3. 저장소 패턴:
    • Mongoose: 모델 기반 접근 방식을 사용합니다.
    • TypeORM: Repository 패턴을 사용하여 데이터베이스 작업을 캡슐화합니다.

NestJS의 모듈화된 구조 덕분에 Mongoose와 TypeORM을 동시에 사용하는 것도 가능하며, 이는 애플리케이션에서 다양한 데이터 소스를 처리해야 할 때 유용합니다.