NestJS 배우기 시리즈 | Dhananjay Kumar


NestJS 학습 가이드 - 완전정복 시리즈

개요

이 문서는 NestJS 프레임워크의 기초부터 고급 기능까지 3단계로 나누어 설명하는 종합적인 학습 가이드입니다. 각 파트는 실제 애플리케이션 개발에 필요한 핵심 개념과 실무 예제를 포함하고 있습니다.


Part 1: NestJS 시작하기

:bullseye: 학습 목표

  • NestJS 개발 환경 구축
  • 첫 번째 애플리케이션 생성
  • Controller, Model, Service의 기본 구조 이해
  • GET/POST 엔드포인트 구현

:clipboard: 사전 요구사항

필수 소프트웨어:

  • Node.js 버전 20 이상
  • Nest CLI

:wrench: 설치 및 설정

Node.js 설치 확인

node -v

Nest CLI 전역 설치

npm i -g @nestjs/cli

프로젝트 생성

nest new api1

:file_folder: 프로젝트 구조

생성된 프로젝트는 다음과 같은 기본 구조를 가집니다:

  • App Controller - 라우팅 처리
  • App Service - 비즈니스 로직
  • App Module - 모듈 구성
  • Main Controller - 애플리케이션 진입점
  • 단위 테스트 파일들

:video_game: 컨트롤러 추가

CLI 명령어

nest generate controller book

생성되는 파일

  • book.controller.ts - 컨트롤러 구현
  • book.controller.spec.ts - 단위 테스트

GET 엔드포인트 구현

@Controller('book')
export class BookController {
    @Get()
    findAll() {
        return 'This action returns all books';
    }

    @Get(':id')
    findOne(@Param('id') id: string) {
        return `This action returns a #${id} book`;
    }
}

:bar_chart: 모델 정의

인터페이스 기반 모델 생성:

export interface IBook {
  id: number;
  title: string;
  author: string;
}

:wrench: 서비스 생성

CLI 명령어

nest generate service book

서비스 구현

@Injectable()
export class BookService {
    private books: IBook[] = [
        { id: 1, title: '1984', author: 'George Orwell' },
        { id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee' },
        { id: 3, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' },
    ];
    
    findAll(): IBook[] {
        return this.books;
    }
    
    findOne(id: number): IBook | undefined {
        return this.books.find(book => book.id === id);
    }
}

:link: 의존성 주입

컨트롤러에서 서비스 사용:

@Controller('book')
export class BookController {
    constructor(private readonly bookService: BookService) {}
    
    @Get()
    findAll(): IBook[] {
      return this.bookService.findAll();
    }
}

:memo: POST 작업 구현

서비스 메서드

create(book: IBook): void {
    this.books.push(book);
}

컨트롤러 엔드포인트

@Post()
create(@Body() book: IBook, @Res() res: Response): void {
    try {
        this.bookService.create(book);
        res.status(HttpStatus.CREATED).send({ message: 'Resource created successfully' });
    } catch (error) {
        res.status(HttpStatus.INTERNAL_SERVER_ERROR).send({ message: 'Internal Server Error' });
    }
}

:warning: 실용적인 팁

  • 메모리 배열을 사용한 데이터 관리는 개발 단계에서만 사용
  • Progress Telerik Fiddler Everywhere로 POST 요청 테스트 가능
  • HTTP 상태 코드를 적절히 사용하여 API 응답 명확화

Part 2: 데이터베이스 연결

:bullseye: 학습 목표

  • SQL Azure 데이터베이스 연결
  • TypeORM 설정 및 사용
  • CRUD 작업 완전 구현
  • Entity 생성 및 관리

:file_cabinet: 데이터베이스 설정

연결 문자열 확인

Azure Portal에서 Connection StringsODBC 탭에서 확인

필수 의존성 설치

npm install @nestjs/typeorm
npm install typeorm mssql

:gear: TypeORM 구성

app.module.ts 설정

@Module({
  imports: [
     TypeOrmModule.forRoot({
      type: 'mssql',
      host: '<your-sql-azure-server>.database.windows.net',
      port: 1433,
      username: '<your-username>',
      password: '<your-password>',
      database: '<your-database>',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true, // ⚠️ 프로덕션에서는 false로 설정
      options: {
        encrypt: true,
      },
    }),
  ],
})

:clipboard: Entity 생성

book.entity.ts

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class Book {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  author: string;
}

:floppy_disk: CRUD 작업 구현

1. 데이터 삽입 (Create)

서비스 메서드:

async create(bookData: Partial<Book>): Promise<Book> {
    const book = this.bookRepository.create(bookData);
    return this.bookRepository.save(book);
}

컨트롤러 엔드포인트:

@Post()
async create(@Body() bookData: Partial<Book>, @Res() res: Response) {
    try {
        const book = await this.bookService.create(bookData);
        return res.status(HttpStatus.CREATED).json(book);
    } catch (error) {
        return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ message: 'Error creating book' });
    }
}

2. 데이터 조회 (Read)

서비스 메서드:

async findAll(): Promise<Book[]> {
    return this.bookRepository.find();
}

async findOne(id: number): Promise<Book | null> {
    return this.bookRepository.findOneBy({ id });
}

3. 데이터 수정 (Update)

async update(id: number, updateData: Partial<Book>): Promise<Book | null> {
    await this.bookRepository.update(id, updateData);
    return this.bookRepository.findOneBy({ id });
}

4. 데이터 삭제 (Delete)

async remove(id: number): Promise<DeleteResult> {
    return this.bookRepository.delete(id);
}

:wrench: 리포지토리 주입

@Injectable()
export class BookService {
  constructor(
    @InjectRepository(Book)
    private readonly bookRepository: Repository<Book>,
  ) {}
}

:warning: 주의사항

  • synchronize: true는 개발 환경에서만 사용
  • ODBC 드라이버 설치 필요
  • 암호화 연결 옵션 활성화 권장

Part 3: 데이터 캐싱

:bullseye: 학습 목표

  • NestJS 캐싱 시스템 이해
  • 인메모리 캐시Redis 캐시 구현
  • 커스텀 키 사용법
  • 성능 최적화 전략

:package: 필수 패키지 설치

npm install @nestjs/cache-manager cache-manager

:wrench: 기본 캐시 설정

AppModule 구성

import { CacheModule } from '@nestjs/cache-manager';

@Module({
  imports: [
    CacheModule.register({
      ttl: 30000, // 30초
      max: 100,
      isGlobal: true,
      store: 'memory',
    }),
  ],
})
export class AppModule {}

:bullseye: 라우트 레벨 캐싱

기본 캐시 인터셉터 사용

@Get()
@UseInterceptors(CacheInterceptor)
@CacheTTL(60) // TTL 오버라이드
async findAll() {
    try {
        return await this.bookService.findAll();
    } catch (error) {
        throw new Error('Error fetching books');
    }
}

:cloud: Azure Redis Cache 통합

Azure Portal 설정

  • 리소스 그룹: nomadcoder
  • 이름: nc
  • 메모리: 0.5 GB
  • 성능: Balanced vCPUs

Redis 캐시 구성

CacheModule.register({
  store: 'redis',
  host: 'your-host-name',
  port: 10000,
  password: 'primary-key-value',
  tls: {},
  ttl: 50000,
  isGlobal: false,
})

:key: 커스텀 키 활용

서비스에서 캐시 매니저 주입

constructor(
    @Inject(CACHE_MANAGER) private cacheManager: Cache
) {}

private readonly bookCacheKey = 'my_custom_books_key';

고급 캐싱 로직

async findAll(): Promise<Book[]> {
    // 캐시에서 먼저 확인
    let books = await this.cacheManager.get<Book[]>(this.bookCacheKey);
    if (books) {
        return books;
    }
    
    // 캐시에 없으면 DB에서 조회 후 캐싱
    books = await this.bookRepository.find();
    await this.cacheManager.set(this.bookCacheKey, books, 1000); 
    return books;
}

:bar_chart: 캐싱 구성 옵션

옵션 설명 기본값
ttl 캐시 생존 시간 (밀리초) 5000
max 최대 캐시 항목 수 100
isGlobal 전역 캐시 사용 여부 false
store 캐시 저장소 타입 ‘memory’

:warning: 캐싱 제한사항

  • GET 엔드포인트만 캐시 가능
  • @Res**() 객체** 사용 시 캐시 인터셉터 작동 불가
  • 네이티브 응답 객체 주입 시 캐싱 비활성화

:rocket: 성능 최적화 팁

커스텀 키 사용의 장점

  1. 정밀한 제어 - 특정 데이터에 직접 접근
  2. 쉬운 무효화 - 키 이름으로 캐시 데이터 삭제
  3. 고성능 - Redis의 키 기반 룩업 최적화
  4. 개별 TTL - 키별로 다른 만료 시간 설정

실용적인 캐싱 전략

  • 자주 조회되는 데이터에 우선적으로 캐싱 적용
  • 변경이 적은 데이터는 긴 TTL 설정
  • 실시간 데이터는 짧은 TTL 또는 캐싱 제외

:books: 추가 학습 리소스

공식 문서

권장 도구

  • Progress Telerik Fiddler Everywhere - API 테스팅
  • Azure Portal - 클라우드 리소스 관리
  • VS Code - 개발 환경 [원문에 없었던 내용]

실습 프로젝트

  1. 기본 도서 관리 API (Part 1)
  2. 데이터베이스 연동 API (Part 2)
  3. 캐싱 적용 고성능 API (Part 3)

:bullseye: 핵심 포인트 요약

Part 1 핵심

  • MVC 패턴 기반의 NestJS 아키텍처
  • 데코레이터 기반 라우팅 시스템
  • 의존성 주입을 통한 서비스 연결

Part 2 핵심

  • TypeORM을 통한 데이터베이스 추상화
  • Entity 기반 데이터 모델링
  • Repository 패턴으로 데이터 액세스

Part 3 핵심

  • 다층 캐싱 전략 구현
  • Redis 캐시 연동으로 확장성 확보
  • 커스텀 키 관리로 정밀한 캐시 제어

:counterclockwise_arrows_button: 다음 단계

(원문 미포함) 이 가이드를 완료한 후 고려해볼 고급 주제들:

  1. 인증/인가 시스템 구현
  2. API 문서화 (Swagger 연동)
  3. 테스팅 전략 수립
  4. 배포 및 DevOps 파이프라인
  5. 마이크로서비스 아키텍처 전환

이 문서는 NestJS 학습 시리즈를 바탕으로 작성되었으며, 실제 프로덕션 환경에서의 적용을 위해서는 추가적인 보안 및 성능 고려사항을 검토해야 합니다.

1개의 좋아요