낙서장이자 오답 노트이자 컨닝 페이퍼

패스트캠퍼스 환급챌린지 22일차 : 코드팩토리의 백엔드 아카데미 : 한 번에 끝내는 NestJS 패키지 - 기초부터 MSA까지 강의 후기 본문

NestJS

패스트캠퍼스 환급챌린지 22일차 : 코드팩토리의 백엔드 아카데미 : 한 번에 끝내는 NestJS 패키지 - 기초부터 MSA까지 강의 후기

NangIn 2025. 4. 22. 18:23

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.

 

강의 내용 정리   

 

Database Migration

 

마이그레이션이란?

  • 정의: 데이터베이스 스키마 변경을 스크립트 형태로 관리하고 실행하는 방식
  • 목적: 통제된 환경에서 데이터베이스 구조를 변경·복구·버전 관리할 수 있게 함

 

왜 마이그레이션이 필요한가?

이유  설명
Controlled Changes 스크립트 기반으로 원하는 시점에 명시적으로 변경 가능
Reversible 변경사항을 되돌릴 수 있음 (down() 메서드 활용)
Versioning 스키마 변경 이력을 추적 가능 → 디버깅 용이
Consistency 여러 환경(dev/staging/prod)에서 스키마를 동일하게 유지
Complex Changes 수동 조작이 필요한 복잡한 작업(관계 수정 등)을 스크립트화

 

마이그레이션의 동작 방식

  • 마이그레이션 파일이 순차적으로 존재 (ex: 1.ts ~ 6.ts)
  • 데이터베이스는 실행된 마이그레이션 상태를 내부에 기록
  • migration:run 실행 시 → 미실행된 마이그레이션만 실행
  • 예:
    • 현재 DB 상태: 2번까지 반영됨
    • 새 파일: 3, 4, 5, 6 → 순서대로 자동 적용됨

 

마이그레이션 구성 요소

  • up() 함수 → 변경 사항 적용
  • down() 함수 → 변경 사항 복구
export class CreateMovieTable1680000000000 implements MigrationInterface {
  async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`CREATE TABLE ...`);
  }

  async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`DROP TABLE ...`);
  }
}

 

마이그레이션 작성 방법

  1. Raw SQL 방식
    • queryRunner.query('ALTER TABLE ...')
    • SQL 커맨드 직접 작성 → 자유도는 높음, 안전성은 낮음
  2. 마이그레이션 API 방식 (TypeORM 지원)
    • 예: queryRunner.createTable(), queryRunner.addColumn() 등
    • 타입스크립트 객체 형태로 선언 → 타입 안전성 ↑

 

자주 쓰는 변경 작업 예시 

작업 Raw SQL  API
테이블 생성 CREATE TABLE createTable()
칼럼 추가 ALTER TABLE ... ADD COLUMN addColumn()
칼럼 이름 변경 RENAME COLUMN renameColumn()
칼럼 타입 변경 ALTER COLUMN ... TYPE changeColumn()
관계 생성(FK) ADD CONSTRAINT FOREIGN KEY createForeignKey()
관계 제거 DROP CONSTRAINT dropForeignKey()

 

마이그레이션 CLI 명령어

  • 마이그레이션 파일 생성
    • npx typeorm migration:generate -n <MigrationName>
      
  • 실행되지 않은 마이그레이션 파일들의 up() 실행
    • npx typeorm migration:run
      
  • 최근 실행된 마이그레이션 하나의 down() 실행
    • npx typeorm migration:revert
      

 

설정 위치

  • typeorm.config.ts 또는 ormconfig.json
    • migrations: ['src/migrations/**/*.ts']
      

 

실무 적용 전략 

상황 적용 방식
개발 환경 synchronize: true로 자동 적용 (개발 속도 ↑)
운영 환경 반드시 migration 기반 배포, 수동 실행 (정합성 확보)
롤백 필요 시 down() 기반 revert 가능 → 안전성 확보

 

Pipe 소개

 

Pipe란?

  • NestJS에서 요청 데이터의 전처리를 담당하는 컴포넌트
  • 컨트롤러에 도달하기 전, 파라미터 혹은 바디 등을 변환(Transform)검증(Validate)

 

요청 흐름 속 Pipe의 위치

  • Request → Middleware → Guard → Interceptor → Pipe → Controller → Service → Repository → Interceptor → Exception Filter → Response
  • 컨트롤러 메서드가 실행되기 직전에 실행됨

 

Pipe의 주요 기능 

기능명 설명
Transformation 요청 데이터를 원하는 타입으로 변환
Validation 요청 데이터가 유효한지 검증

 

Pipe 적용 범위 및 우선순위 

적용 위치 메서드  우선순위
글로벌 app.useGlobalPipes() 1순위 (전역)
컨트롤러 @UsePipes() 2순위
라우트 @UsePipes() 메서드 단위 3순위
파라미터 @Param('id', CustomPipe) 등 4순위 (가장 우선 적용)

아래로 갈수록 우선순위 높음

  • Global Pipe
    • import { ValidationPipe } from '@nestjs/common';
      import { NestFactory } from '@nestjs/core';
      import { AppModule } from './app.module';
      
      async function bootstrap() {
      	const app = await NestFactory.create(AppModule);
      	app.useGlobalPipes(new ValidationPipe());
      	await app.listen(3000);
      }
      bootstrap();
      
  • Controller Pipe
    • @Contreller('movie')
      @UsePipes(new ValidationPipe())
      export class MovieController {
      	constructor(private readonly movieService: MovieService) {}
      	
      	@Get()
      	getMovies(@Query('title') title?: string) {
      		return this.movieService.findAll(title);
      	}
      }
      
  • Route Pipe
    • @Contreller('movie')
      export class MovieController {
      	constructor(private readonly movieService: MovieService) {}
      	
      	@Patch(':id')
      	@UsePipes(new ValidationPipe())
      	patchMovies(@Param('id') id: string, @Body() body: UpdateMovieDto) {
      		return this.movieService.update(+id, body);
      	}
      }
      
  • Route Parameter Pipe
    • @Contreller('movie')
      export class MovieController {
      	constructor(private readonly movieService: MovieService) {}
      	
      	@Patch(':id')
      	patchMovies(@Param('id', ParseIntPipe) id: number, @Body() body: UpdateMovieDto) {
      		return this.movieService.update(id, body);
      	}
      }
      

 

기본 내장 Pipe 종류 

Pipe 이름 기능 설명
ValidationPipe class-validator를 기반으로 유효성 검사 수행
ParseIntPipe 문자열 → 정수 변환, 실패 시 BadRequestException
ParseFloatPipe 문자열 → 실수 변환
ParseBoolPipe `'true'
ParseUUIDPipe UUID 형식인지 검증
DefaultValuePipe 값이 없을 경우 기본값 제공
ParseArrayPipe 배열 여부 확인 (단, 변환은 직접 해야 할 수도 있음)

 

ValidationPipe 작동 방식 요약

  • 클래스 기반 DTO에 @IsString(), @IsInt() 등 선언
  • ValidationPipe가 이 메타데이터를 바탕으로 자동 검증
  • DTO가 제대로 구성돼 있어야 작동 가능

 

커스텀 Pipe 작성 방법

@Injectable()
export class ParseIntCustomPipe implements PipeTransform<string, number> {
  transform(value: string): number {
    const parsed = parseInt(value, 10);
    if (isNaN(parsed)) {
      throw new BadRequestException('정수가 아닙니다.');
    }
    return parsed;
  }
}
  • @Injectable() → DI 컨테이너에 등록
  • PipeTransform<TInput, TOutput> 구현 → transform(value, metadata) 메서드 필수
  • 에러 발생 시 BadRequestException, NotFoundException 등 직접 던지면 됨

 

Pipe를 가장 자주 쓰는 위치는?

  • Route Parameter
    • 파라미터 유효성 검사에 가장 많이 활용
    • ex: @Param('id', ParseIntPipe)

 

학습 후기         

 

이번 강의를 통해 TypeORM에서 제공하는 마이그레이션 기능의 중요성과 사용법을 명확하게 이해할 수 있었습니다. 그동안 개발 중에는 synchronize: true 옵션으로 편리하게 테이블을 자동 생성하고 수정해왔지만, 이 방식이 운영 환경에서는 치명적인 문제가 될 수 있다는 점을 다시 한 번 체감했습니다. 특히 migration:run, migration:revert 등의 명령어를 통해 DB 변경 이력을 관리하고, 실수로 변경된 스키마를 되돌릴 수 있다는 점이 매우 강력하다고 느꼈습니다.

 

개인적으로 인상 깊었던 점은 "모든 스키마 변경은 문서화되고 버전으로 관리된다"는 개념이었습니다. 이는 협업 시 다른 개발자들과 DB 상태를 일관되게 맞출 수 있게 해주고, 예상치 못한 에러에 대한 디버깅 포인트도 명확히 해줍니다. 향후 실무에서도 배포 전에는 반드시 마이그레이션 파일을 작성하고, synchronize는 제거한 상태로 운영 서버를 다루는 습관을 들여야겠다고 다짐했습니다.

 

그리고 또한 NestJS에서의 Pipe는 단순히 입력값을 필터링하는 도구 이상이라는 점을 이번 강의를 통해 확실히 체감했습니다. 특히 전처리 과정을 컨트롤러 전에 처리함으로써, 서비스 계층의 로직을 더 깔끔하게 유지할 수 있고, 예외 상황도 더 명확하게 관리할 수 있다는 점이 매우 실용적으로 다가왔습니다.

 

그중에서도 가장 자주 쓰게 되는 위치는 역시 파라미터입니다. 예를 들어 @Param('id', ParseIntPipe)처럼 단순한 검증이지만, 이를 빼먹었을 때 발생할 수 있는 에러를 생각하면 꼭 적용해야 할 필수 요소라는 생각이 들었습니다.

 

또한 ValidationPipe를 글로벌로 한 번만 설정해두면, 이후 모든 DTO 유효성 검증이 자동으로 적용되는 것도 실무 효율성을 크게 높여주는 요소라고 느꼈습니다. 커스텀 파이프를 직접 구현해보며, 단순한 타입 체크 이상의 복잡한 유스케이스도 대응 가능하다는 점에서 확장성 또한 인상 깊었습니다.

 

학습 인증샷       

                       

수강 인증 사진
학습 인증샷
공부 시작 시간

 

공부 종료 시간

https://abit.ly/lisbva