개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

NestJS Service — 비즈니스 로직을 담당하는 클래스

2025년 2월 23일
NestJSService@InjectableBusiness LogicRepository

NestJS에서 Service는 애플리케이션의 핵심 비즈니스 로직을 담당하는 클래스다. Controller가 HTTP 요청을 받아 응답을 돌려주는 역할만 한다면, 실제 데이터를 처리하고 규칙을 적용하는 일은 Service가 맡는다. 이 분리 덕분에 코드는 단순해지고, 테스트는 훨씬 쉬워진다.

Controller → Service → Repository 흐름

NestJS 애플리케이션은 세 계층으로 나뉜다.

HTTP Request
     │
     ▼
┌─────────────┐
│  Controller │  ← 요청/응답 처리, 라우팅
└──────┬──────┘
       │ 호출
       ▼
┌─────────────┐
│   Service   │  ← 비즈니스 로직
└──────┬──────┘
       │ 호출
       ▼
┌─────────────┐
│ Repository  │  ← DB 접근
└─────────────┘

Controller는 Service를 호출하고, Service는 Repository를 통해 데이터를 읽거나 쓴다. 각 계층은 자신의 책임만 진다.

@Injectable 데코레이터

Service 클래스에는 반드시 @Injectable 데코레이터를 붙인다. 이 데코레이터는 NestJS의 DI(Dependency Injection) 컨테이너에 해당 클래스를 등록한다.

@Injectable — NestJS IoC 컨테이너가 이 클래스의 인스턴스를 생성하고 주입할 수 있도록 메타데이터를 추가하는 데코레이터.

import { Injectable } from '@nestjs/common';

@Injectable()
export class UsersService {
  private readonly users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
  ];

  findAll() {
    return this.users;
  }

  findOne(id: number) {
    return this.users.find((u) => u.id === id);
  }
}

Controller에 Service 주입하기

Service는 Controller 생성자에서 주입받는다. NestJS가 인스턴스를 자동으로 생성해 넘겨주므로 new UsersService를 직접 호출할 필요가 없다.

import { Controller, Get, Param } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get()
  findAll() {
    return this.usersService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.usersService.findOne(Number(id));
  }
}

Service를 Module의 providers 배열에 등록해야 DI가 동작한다.

import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

단일 책임 원칙과 테스트 용이성

Service가 비즈니스 로직을 독립적으로 가지면 두 가지 이점이 생긴다.

이점설명
단일 책임Controller는 HTTP만, Service는 로직만 담당
테스트 용이성HTTP 없이 Service만 단독으로 유닛 테스트 가능

단일 책임 원칙(SRP) — 하나의 클래스는 하나의 이유로만 변경되어야 한다는 객체지향 설계 원칙.

아래처럼 Service만 격리해 테스트할 수 있다.

import { UsersService } from './users.service';

describe('UsersService', () => {
  let service: UsersService;

  beforeEach(() => {
    service = new UsersService();
  });

  it('전체 사용자를 반환한다', () => {
    expect(service.findAll()).toHaveLength(2);
  });
});

Controller나 HTTP 레이어 없이 순수하게 로직만 검증할 수 있어 테스트가 빠르고 명확하다. Repository 패턴을 추가하면 DB 의존성도 Mock으로 교체할 수 있어 테스트 격리가 더욱 완전해진다.

← 이전 글NestJS Controller — HTTP 요청을 처리하는 클래스
다음 글 →NestJS Provider — DI 컨테이너가 관리하는 객체