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

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

NestJS

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

NangIn 2025. 4. 4. 20:22

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

 

강의 내용 정리                                                                                                                   

Dependency Injection (DI) & Inversion of Control (IoC)

 

Dependency Injection (의존성 주입)

  • 클래스 A가 클래스 B를 사용해야 할 때, 일반적으로 내부에서 new B()로 인스턴스를 직접 생성
    • 강한 결합(Tight Coupling), 유지보수 어려움
  • DI 방식:
    	class A {
      constructor(private b: B) {}
    }
    
  • 클래스 A는 B의 인스턴스를 생성자(constructor)를 통해 외부로부터 주입받음
  • 장점:
    • 느슨한 결합(Loose Coupling)
    • 테스트 용이 (Mock 객체 주입 가능)
    • 중복 인스턴스 없이 공통 인스턴스 재사용 가능

 

Inversion of Control (제어의 역전)

  • 객체 생성과 주입의 제어권을 개발자가 아닌 프레임워크(NestJS)에 넘기는 개념
  • NestJS 내부의 IoC 컨테이너가 필요한 클래스를 자동으로 생성하고 주입해줌
    • → 개발자는 생성에 신경 쓸 필요 없이 타입만 선언하면 됨

 

NestJS에서의 적용 흐름

  1. @Injectable()을 클래스에 붙이면 IoC 컨테이너에 등록됨
  2. 필요할 때 constructor(private b: BService)로 주입 선언
  3. NestJS가 해당 클래스 인스턴스를 자동 생성하고 필요한 곳에 주입
  4. 동일한 인스턴스를 여러 곳에서 공유하여 사용 (싱글톤처럼 작동)

 

DI와 IoC 관계

  • DIIoC를 구현하는 방식 중 하나
  • NestJS는 IoC 컨테이너를 기반으로 DI를 자동으로 수행함

 

Dependency injection and Inversion of Control NestJS 코드에서 확인해보기

 

AppService 클래스 구조 이해

  • app.service.ts에 정의된 클래스는 @Injectable() 데코레이터가 붙어 있음
    • → NestJS의 IOC 컨테이너가 이 클래스를 관리함
  • @Injectable()의 역할:
    • → "이 클래스를 IoC 컨테이너에서 관리하고, 필요한 곳에 주입해줘" 라는 뜻

 

AppController에서 서비스 주입 확인

  • app.controller.ts를 보면, 생성자 안에 AppService가 의존성으로 주입됨
constructor(private readonly appService: AppService) {}
  • 즉, NestJS는 AppController를 생성할 때, AppService 인스턴스를 자동으로 생성하고 주입해줌
    • Dependency Injection (DI)

 

그럼 누가 이걸 인스턴스화하나?

  • main.ts에는 직접 인스턴스 생성 코드 없음 → NestJS가 대신 수행
  • 실질적인 인스턴스 생성과 의존성 주입은 NestJS의 IoC 컨테이너에서 자동 수행됨

 

AppModule에서 구조적으로 선언

  • app.module.ts에서 다음과 같이 구성되어 있음:
@Module({
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
  • 여기서 선언만 해주면, NestJS는:
    • AppController에 정의된 내용을 기반으로 엔드포인트 생성
    • AppService를 인스턴스화하여 필요한 곳에 주입

 

IoC 컨테이너의 역할 요약

  • 클래스가 @Injectable()로 정의되어 있고 모듈의 providers 배열에 등록되어 있다면 NestJS의 IoC 컨테이너는
    1. 인스턴스를 자동 생성
    2. 필요한 컨트롤러 또는 다른 서비스의 생성자에 자동 주입

 

Express와의 차이점

  • Express에서는 서비스 인스턴스 생성, 의존성 주입, 연결 로직을 직접 구현해야 함
  • NestJS에서는:
    • 위의 모든 것을 프레임워크가 자동으로 처리
    • 단, 구조만 맞추면 됨 (모듈 → 컨트롤러 → 서비스)

 

서비스 분리와 책임 분리 (Controller vs Service)

 

왜 서비스로 로직을 분리해야 할까?

  • NestJS의 아키텍처는 역할 분리를 명확히 하는 것이 원칙
    • → 유지보수를 쉽게 하고, 에러의 원인을 빠르게 파악하기 위해 로직을 분리
  • Controller의 책임
    • 요청 데이터 수신 (Param, Query, Body 등)
    • 타입 및 유효성 검증
  • Service의 책임
    • 실제 비즈니스 로직 수행
    • 데이터 가공, 처리, 반환

 

코드 분리 흐름

  1. 기존 컨트롤러에 있던 공통 데이터(movies, idCounter, Movie 인터페이스)를 서비스로 이동
    • 인터페이스는 export 키워드를 붙여 외부에서 사용할 수 있도록 설정
  2. 각 요청 유형에 맞게 서비스 메서드 정의
// **GET 전체/조건 조회: getManyMovies(title?: string)**
getManyMovies(title?: string): Movie[] {
  if (!title) return this.movies;
  return this.movies.filter((m) => m.title.startsWith(title));
}

// **GET 단건 조회: getMovie(id: number)**
getMovieById(id: number): Movie {
  const movie = this.movies.find((m) => m.id === id);
  if (!movie) throw new NotFoundException('존재하지 않는 영화입니다.');
  return movie;
}

// **POST 생성: createMovie(title: string)**
createMovie(title: string): Movie {
  const movie = { id: this.idCounter++, title };
  this.movies.push(movie);
  return movie;
}

// **PATCH 수정: updateMovie(id: number, title: string)**
updateMovie(id: number, title: string): Movie {
  const movie = this.movies.find((m) => m.id === id);
  if (!movie) throw new NotFoundException('존재하지 않는 영화입니다.');
  Object.assign(movie, { title });
  return movie;
}

// **DELETE 삭제: deleteMovie(id: number)**
deleteMovie(id: number): { id: number } {
  const index = this.movies.findIndex((m) => m.id === id);
  if (index === -1) throw new NotFoundException('존재하지 않는 영화입니다.');
  this.movies.splice(index, 1);
  return { id };
}
  1. 컨트롤러에서는 각 요청에 대해 서비스 메서드를 위임 호출만 담당
// Controller

@Get()
getMovies(@Query('title') title?: string) {
  return this.appService.getManyMovies(title);
}

@Get(':id')
getMovie(@Param('id') id: string) {
  return this.appService.getMovieById(+id);
}

@Post()
createMovie(@Body('title') title: string) {
  return this.appService.createMovie(title);
}

@Patch(':id')
updateMovie(@Param('id') id: string, @Body('title') title: string) {
  return this.appService.updateMovie(+id, title);
}

@Delete(':id')
deleteMovie(@Param('id') id: string) {
  return this.appService.deleteMovie(+id);
}

 

서비스 주입 확인 (DI + IoC 작동 확인)

  • AppService에 @Injectable() 선언 → IoC 컨테이너가 인스턴스 관리
  • app.module.ts에서 AppService를 providers에 등록
  • NestJS는 AppController를 생성할 때 자동으로 AppService 주입

 

모듈의 providers에 넣는 이름은 왜 service인가?

  • Service는 Provider의 일종이다.
    • Service는 주로 비즈니스 로직을 처리하는 클래스
    • @Injectable()로 선언 → DI 대상이 됨
  • Provider는 더 넓은 개념이다.
    • DI 컨테이너가 주입할 수 있는 모든 대상
    • 예: Service, Repository, Guard, Pipe, Factory 등
  • 왜 이름을 'Service'라고 지을까?
    • 역할에 따라 명명
    • 로직 처리용 클래스는 관례적으로 SomethingService로 이름 짓는 것

 

핵심 포인트 요약

  • 컨트롤러는 요청만 받음 → 로직은 서비스로 위임
  • NestJS는 @Injectable()과 providers 등록만 해주면 → IoC 컨테이너가 자동 인스턴스 생성 + 주입
  • 구조가 명확하면 유지보수도 쉽고, 테스트도 용이

 

 

학습 후기                                                                                                                                       

NestJS를 처음 접했을 때 가장 헷갈렸던 개념이 바로 서비스와 프로바이더의 관계, 그리고 DI와 IoC이었습니다. 이번 학습을 통해 단순히 코드를 따라치는 것이 아니라, NestJS가 설계된 아키텍처 철학 자체를 이해할 수 있었던 점이 가장 큰 수확이었습니다.

 

특히 인상 깊었던 건, NestJS에서는 비즈니스 로직을 서비스로 분리하고, 컨트롤러는 오직 요청만 담당하는 구조를 강제한다는 점입니다. 이 덕분에 유지보수성과 테스트 가능성이 눈에 띄게 높아지고, 에러의 원인도 쉽게 추적할 수 있었습니다. 과거에는 하나의 파일에서 모든 로직을 처리하면서 생겼던 복잡함이 NestJS의 구조 덕분에 말끔히 정리되는 느낌이었습니다.

 

DI와 IoC의 원리를 실제 코드에서 확인할 수 있었던 것도 매우 유익했습니다. @Injectable()로 선언된 클래스가 IoC 컨테이너에 등록되고, 모듈에서 providers에만 추가해주면 어디서든 자동으로 주입된다는 구조는 처음에는 신기했고, 익숙해지니 매우 효율적이라는 걸 느꼈습니다. 특히 인스턴스를 직접 관리하지 않아도 된다는 점은 생산성 측면에서 큰 장점이었습니다.

 

추가로, 서비스는 단지 NestJS의 관례일 뿐, 본질적으로는 Provider의 하위 개념이라는 점도 명확히 이해할 수 있었습니다. 이처럼 개념과 구조가 정리되니 코드의 방향성과 역할이 눈에 들어오기 시작했고, 자연스럽게 NestJS를 ‘프레임워크’가 아닌 ‘설계 철학’으로 받아들일 수 있었습니다.

 

이번 학습을 통해 NestJS의 기반이 되는 구조적 사고방식을 익힐 수 있었고, 이는 앞으로의 프로젝트 설계에 큰 자산이 될 거라 확신합니다. NestJS는 단순한 툴이 아니라, 협업과 유지보수를 고려한 체계적인 백엔드 아키텍처라는 점에서 매우 훌륭한 프레임워크임을 다시 한번 느낄 수 있었습니다.

 

학습 인증샷                                                                                                                                   

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

https://abit.ly/lisbva

 

Abit.ly 다운받기

 

abit.ly