NestJS

Nest.js는 효율적이고 확장 가능한 Node.js 서버 측 애플리케이션을 구축하기 위한 프레임워크.
Express.js를 기반으로 하며, TypeScript를 완벽하게 지원한다.
Angular의 아키텍처에서 영감을 받아 설계되었으며, 모듈식 아키텍처를 통해 애플리케이션을 체계적으로 구성할 수 있게 해주며, 의존성 주입(Dependency Injection)과 데코레이터 패턴을 적극적으로 활용한다.

주요 특징

  1. 모듈화된 구조

    • 애플리케이션을 기능별로 모듈화하여 관리
    • 각 모듈은 독립적으로 동작하면서도 서로 연결 가능
  2. 의존성 주입

    • 컴포넌트 간의 결합도를 낮추고 테스트 용이성 향상
    • 서비스와 컨트롤러 간의 관계를 명확하게 정의
  3. 미들웨어 지원

    • Express 미들웨어 완벽 지원
    • 커스텀 미들웨어 작성 가능
  4. 예외 필터

    • 중앙집중식 예외 처리 메커니즘
    • 커스텀 예외 필터 구현 가능
  5. 파이프

    • 입력 데이터 검증과 변환
    • 커스텀 파이프 구현 가능

장점

  1. 체계적인 아키텍처 제공으로 대규모 애플리케이션 개발에 적합
  2. Express.js와의 완벽한 호환성
  3. 강력한 CLI 도구 제공
  4. 풍부한 문서화와 활발한 커뮤니티
  5. 테스트 용이성이 높음

단점 및 한계

  1. 학습 곡선이 상대적으로 가파름
  2. 작은 프로젝트에는 과도한 구조일 수 있음
  3. JavaScript로 사용 시 데코레이터 지원을 위한 추가 설정 필요
  4. TypeScript에 비해 JavaScript 사용 시 일부 기능 제한

사용 방법

  1. 먼저 NestJS CLI를 설치하고 새 프로젝트를 생성합니다:

    1
    2
    
    npm i -g @nestjs/cli
    nest new project-name
    
  2. JavaScript로 개발할 때 필요한 기본 설정:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    // package.json
    {
      "type": "module",
      "dependencies": {
        "@nestjs/common": "^8.0.0",
        "@nestjs/core": "^8.0.0",
        "@nestjs/platform-express": "^8.0.0",
        "reflect-metadata": "^0.1.13"
      }
    }
    

주요 명령어

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 새 모듈 생성
nest generate module users

# 새 컨트롤러 생성
nest generate controller users

# 새 서비스 생성
nest generate service users

# 애플리케이션 실행
npm run start

# 개발 모드로 실행 (자동 재시작)
npm run start:dev

예시 코드

기본적인 CRUD 기능을 갖춘 사용자 관리 애플리케이션

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
// main.js
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module.js';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

// app.module.js
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module.js';

@Module({
  imports: [UsersModule],
})
export class AppModule {}

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

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

// users/users.controller.js
import { Controller, Get, Post, Body, Param, Delete, Put } from '@nestjs/common';
import { UsersService } from './users.service.js';

@Controller('users')
export class UsersController {
  constructor(usersService) {
    this.usersService = usersService;
  }

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

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

  @Post()
  create(@Body() userData) {
    return this.usersService.create(userData);
  }

  @Put(':id')
  update(@Param('id') id, @Body() userData) {
    return this.usersService.update(id, userData);
  }

  @Delete(':id')
  remove(@Param('id') id) {
    return this.usersService.remove(id);
  }
}

// users/users.service.js
import { Injectable } from '@nestjs/common';

@Injectable()
export class UsersService {
  constructor() {
    this.users = [];
  }

  findAll() {
    return this.users;
  }

  findOne(id) {
    return this.users.find(user => user.id === parseInt(id));
  }

  create(userData) {
    const newUser = {
      id: this.users.length + 1,
      userData,
    };
    this.users.push(newUser);
    return newUser;
  }

  update(id, userData) {
    const user = this.findOne(id);
    if (!user) return null;
    
    Object.assign(user, userData);
    return user;
  }

  remove(id) {
    const index = this.users.findIndex(user => user.id === parseInt(id));
    if (index === -1) return null;
    
    return this.users.splice(index, 1)[0];
  }
}

미들웨어 사용 예시

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// middleware/logger.middleware.js
import { Injectable, NestMiddleware } from '@nestjs/common';

@Injectable()
export class LoggerMiddleware {
  use(req, res, next) {
    console.log(`Request… ${req.method} ${req.url}`);
    next();
  }
}

// app.module.js에 미들웨어 적용
configure(consumer) {
  consumer
    .apply(LoggerMiddleware)
    .forRoutes('*');
}

예외 필터 사용 예시

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// filters/http-exception.filter.js
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';

@Catch(HttpException)
export class HttpExceptionFilter {
  catch(exception, host) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
        message: exception.message,
      });
  }
}

파이프 사용 예시

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// pipes/validation.pipe.js
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ValidationPipe {
  transform(value, metadata) {
    if (!value) {
      throw new BadRequestException('No data submitted');
    }
    return value;
  }
}

참고 및 출처

NestJS - A progressive Node.js framework