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

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

NestJS

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

NangIn 2025. 4. 17. 08:07

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

 

강의 내용 정리   

 

Many to One Relationship 생성하기

 

관계 설계 개념

  • 감독(Director)여러 영화(Movie) 를 연출할 수 있음
    • 즉, 감독(1) ↔ 영화(N) 관계 → OneToMany / ManyToOne
    • 감독은 독립된 도메인 → 별도 모듈로 생성
    • 무비 디테일처럼 Movie에 종속적이지 않음 → 독립 API 제공 가능성 있음

 

감독(Director) 모듈 생성

  • Nest CLI 명령어 사용:
    • nest g resource director
  • 기본 구성 파일 생성됨:
    • director.module.ts
    • director.service.ts
    • director.controller.ts
    • director.entity.ts
    • create-director.dto.ts, update-director.dto.ts

 

BaseTable 분리

  • 공통 엔티티 분리:
    • BaseTable은 createdAt, updatedAt, version을 포함
    • 기존 Movie 내부에 정의된 BaseTable → common/entity/base-table.entity.ts로 이동
  • 적용 방법:
    • Movie, Director 모두 extends BaseTable

 

Director 엔티티 정의

  • @Entity()
    export class Director extends BaseTable {
      @PrimaryGeneratedColumn()
      id: number;
    
      @Column()
      name: string;
    
      @Column()
      dob: string; // 생년월일
    
      @Column()
      nationality: string;
    
      @OneToMany(() => Movie, (movie) => movie.director)
      movies: Movie[];
    }
    

 

Movie 엔티티에 관계 추가

  • @ManyToOne(() => Director, (director) => director.id)
    director: Director;
    
    • foreign key 자동 생성됨 → directorId
    • 관계 설정 시:
      • Movie는 Many → ManyToOne 사용
      • Director는 One → OneToMany 사용

 

DTO 구성

  • CreateDirectorDto
    • export class CreateDirectorDto {
        @IsNotEmpty()
        name: string;
      
        @IsNotEmpty()
        @IsDateString()
        dob: string;
      
        @IsNotEmpty()
        nationality: string;
      }
      
  • UpdateDirectorDto
    • export class UpdateDirectorDto {
        @IsOptional()
        name?: string;
      
        @IsOptional()
        @IsDateString()
        dob?: string;
      
        @IsOptional()
        nationality?: string;
      }
      

 

AppModule 등록

  • AppModule 또는 해당 Feature Module에 다음 추가:
    • TypeOrmModule.forFeature([Director, Movie])
      

 

결과 확인

  • DB 테이블 구조
    • Movie 테이블  
      컬럼 설명
      id PK
      title 제목
      genre 장르
      directorId FK (Director.id)
    • Director 테이블 
      컬럼 설명
      id PK
      name 감독 이름
      dob 생일
      nationality 국적
      createdAt / updatedAt / version BaseTable 상속

 

정리 요약 

항목 설명
관계 방향 감독 1명 → 여러 영화
사용 관계 OneToMany / ManyToOne
분리 여부 감독은 독립 도메인 → 모듈 따로 생성
실무 감각 무비디테일과 다르게, 재사용 가능성이 있는 도메인은 모듈로 분리
DB 결과 영화 테이블에 directorId FK 자동 생성됨

 

Director 엔드포인트 완성하기

 

기본 구조 정비

  • 컨트롤러 메서드 순서 정렬
    • get → post → patch → delete 순으로 정렬
    • 실제 기능에는 영향 없지만, 가독성과 유지보수 용이

 

의존성 및 모듈 설정

  • TypeORM 모듈 등록
    • @Module({
        imports: [TypeOrmModule.forFeature([Director])],
        controllers: [DirectorController],
        providers: [DirectorService],
      })
      
  • Service 의존성 주입
    • constructor(
        @InjectRepository(Director)
        private readonly directorRepository: Repository<Director>,
      ) {}
      

 

CRUD 메서드 구현

  • Create
    • async create(createDto: CreateDirectorDto) {
        return this.directorRepository.save(createDto);
      }
      
  • Read
    • // 전체 조회
      async findAll() {
        return this.directorRepository.find();
      }
      
      // 단일 조회
      async findOne(id: number) {
        return this.directorRepository.findOne({
          where: { id },
        });
      }
      
  • Update
    • async update(id: number, updateDirectorDto: UpdateDirectorDto) {
        const director = await this.directorRepository.findOne({ where: { id } });
        if (!director) throw new NotFoundException();
      
        await this.directorRepository.update({ id }, { ...updateDirectorDto });
        const newDirector = await this.directorRepository.findOne({
          where: { id },
        });
      
        return newDirector;
      }
      
      • UpdateDirectorDto는 모든 필드에 @IsOptional() 설정 필요
  • Delete
    • async remove(id: number) {
        const director = await this.directorRepository.findOne({ where: { id } });
        if (!director) throw new NotFoundException();
      
        await this.directorRepository.delete(id);
        return id;
      }
      

 

정리 요약 

항목 요약
목적 Director에 대한 CRUD 완성
방식 Nest CLI의 Resource Generator 사용
핵심 포인트 @IsOptional() 누락 시 PATCH 오류 주의
결과 엔드포인트 5개 전부 정상 작동 및 Postman 테스트 완료
다음 작업 Movie와 Director 간의 ManyToOne 관계 연동 처리

 

학습 후기         

 

NestJS로 ManyToOne 관계를 직접 구성해보면서, 도메인 간 관계를 어떻게 설계하고 분리할지에 대한 감각이 확실히 잡혔습니다. 특히 이번 실습에서는 단순히 관계를 연결하는 것에서 그치지 않고, 도메인을 독립된 모듈로 나누는 기준에 대한 고민이 인상 깊었습니다. 예를 들어 MovieDetail은 Movie에 종속되어 있기 때문에 하나의 흐름으로 묶어 처리한 반면, Director는 추후 재사용 가능성이 있는 독립된 도메인으로 판단하고 별도의 모듈로 분리한 것이 대표적인 예입니다. 이를 통해 실무에서도 API 단위로 도메인을 나눌 때의 기준을 세우는 감각을 익힐 수 있었습니다.

 

또한 @ManyToOne, @OneToMany 관계를 설정할 때 foreign key가 어떤 방식으로 생성되고 어떤 테이블에 들어가는지 눈으로 확인하며 실습한 점이 매우 도움이 되었습니다. 특히 TypeORM에서 제공하는 관계 설정이 생각보다 직관적이고 유연하다는 점이 좋았습니다. 코드 한 줄로 foreign key가 자동으로 생성되고, cascade나 nullable, eager 같은 옵션들로 동작 방식을 세밀하게 조정할 수 있다는 점에서 ORM의 강력함을 실감했습니다.

 

Postman으로 직접 API를 호출하면서 CRUD를 테스트할 때마다 즉각적으로 DB에 반영되는 것을 보며, 이 구조가 실제 서비스에서 어떻게 활용될 수 있을지를 자연스럽게 떠올릴 수 있었습니다. 특히 @IsOptional()을 누락하면 PATCH 요청이 실패하는 상황은 실무에서 흔히 겪을 수 있는 실수라 앞으로도 주의가 필요하겠다는 생각이 들었습니다.

 

마지막으로 느낀 점은, 관계 설정은 단순히 데이터 모델을 연결하는 게 아니라 도메인 설계와 API UX까지도 고려하는 일이라는 점입니다. '감독을 생성할 때 영화를 연결하는가, 아니면 영화를 생성할 때 감독을 연결하는가?' 같은 고민이 단순한 기술적 선택이 아니라, 실제 서비스 플로우에 맞춘 사용자 중심 설계라는 것을 이번 실습을 통해 몸소 느꼈습니다. 단순히 기능 구현을 넘어서, 비즈니스 흐름과 도메인 구조에 맞는 설계를 고려하는 사고가 길러졌습니다.

 

학습 인증샷       

                       

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

https://abit.ly/lisbva