Javascript/NestJS

프로젝트 구조

kyoulho 2023. 3. 13. 18:45

NestJS CLI 설치


NestJS CLI는 NestJS 애플리케이션을 개발할 때 사용하는 Command Line Interface입니다. NestJS CLI를 사용하면 NestJS 프로젝트 구조를 자동으로 생성하거나, 모듈, 컨트롤러, 서비스 등의 구성요소를 생성할 수 있습니다. 또한, 프로젝트 빌드, 실행, 테스트 등의 작업을 쉽게 수행할 수 있습니다. NestJS CLI는 NestJS 애플리케이션 개발의 생산성을 높여주는 매우 유용한 도구입니다.

npm i -g @nestjs/cli

 

프로젝트 생성

아래 명령어로 NestJS 프로젝트를 생성하게 되면 [프로젝트 이름]으로 된 디렉토리가 생성됩니다.

nest new [프로젝트 이름]

 

디렉토리 구조

프로젝트가 생성된 디렉토리의 구조입니다.

├── README.md
├── .eslintrc.js
├── .prettierrc
├── nest-cli.json
├── package-lock.json
├── package.json
├── node_modules
├── src
│   ├── app.controller.spec.ts
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   └── main.ts
├── test
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
├── tsconfig.build.json

이제 부터 하나씩 어떤 파일이 어떤 일을 하는지 파악해봅시다.

 

README.md

README.md를 모르는 개발자는 없을거라고 생각하지만 혹시 모르니 간단히 설명해 보겠습니다.
README.md 파일은 프로젝트의 소개와 설명, 사용 방법 등을 담은 문서입니다. 프로젝트의 루트 디렉토리에 위치하며, GitHub 등의 코드 저장소에서도 자동으로 인식되어 프로젝트 설명 페이지에 표시됩니다. 일반적으로 다음과 같은 내용이 포함됩니다.

  • 프로젝트의 이름, 소개, 주요 기능 등
  • 프로젝트를 사용하기 위한 설치 및 사용 방법
  • 프로젝트 구성 요소 및 구조
  • 프로젝트에 기여하는 방법 및 개발자 정보
  • 라이선스 및 저작권 정보

 

.eslintrc.js

ESLint 설정 파일입니다. ESLint는 자바스크립트와 타입스크립트 코드에 문법 에러를 잡아줍니다.

module.exports = {
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: 'tsconfig.json',
    tsconfigRootDir: __dirname,
    sourceType: 'module',
  },
  plugins: ['@typescript-eslint/eslint-plugin'],
  extends: [
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
  ],
  root: true,
  env: {
    node: true,
    jest: true,
  },
  ignorePatterns: ['.eslintrc.js'],
  rules: {
    '@typescript-eslint/interface-name-prefix': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
  },
};
  • parser: 코드를 분석하는 데 사용할 파서를 지정합니다. @typescript-eslint/parser가 지정되어 있으며, TypeScript 코드의 문법을 파싱합니다.
  • parserOption: 파서의 옵션을 지정합니다. TypeScript 프로젝트의 설정 파일인 tsconfig.json을 참조하도록 지정되어 있습니다. tsconfigRootDir는 설정 파일의 루트 디렉토리를 지정하고, sourceType는 모듈의 타입을 지정합니다.
  • plugins: 사용할 ESLint 플러그인을 지정합니다. @typescript-eslint/eslint-plugin 플러그인이 지정되어 있으며, TypeScript 코드에서만 사용할 규칙을 제공합니다.
  • extends: 사용할 ESLint 규칙 세트를 지정합니다. @typescript-eslint/recommended와 prettier/recommended 규칙 세트가 지정되어 있습니다. @typescript-eslint/recommended는 TypeScript 코드에 대한 일반적인 규칙을 제공하고, prettier/recommended는 코드 스타일에 관련된 규칙을 제공합니다.
  • root: ESLint가 해당 프로젝트의 최상위 디렉토리에서부터 시작하여 설정 파일을 찾아가도록 지정합니다.
  • env: 사용할 환경을 지정합니다. node와 jest 환경을 지정하며, Node.js와 Jest를 사용하는 프로젝트에서 사용됩니다.
  • ignorePatterns: ESLint가 무시할 파일 패턴을 지정합니다.
  • rules: 사용할 규칙을 지정합니다. TypeScript 코드에서의 몇 가지 규칙을 무시하도록 지정되어 있습니다.

 

.prettierrc

Prettier 는 코드 스타일을 일관되게 유지해주는 포맷터입니다. ESLint는 문법의 오류를 Prettier는 문법의 형식을 책임집니다.

{
  "singleQuote": true,
  "trailingComma": "all"
}
  • singleQuote: 코드에서 문자열을 따옴표로 감쌀 것인지, 쌍따옴표로 감쌀 것인지를 지정합니다. true로 설정되어 있으므로, 문자열은 따옴표로 감쌀 것입니다.
  • trailingComma: 객체나 배열의 마지막 요소 뒤에 쉼표를 붙일 것인지를 지정합니다. all로 설정되어 있으므로, 마지막 요소 뒤에 쉼표를 붙일 것입니다.

 

nest-cli.json

NestJS CLI 에서 NestJS 애플리케이션을 생성하고 실행 하기 위한 옵션 및 설정을 지정하는 json 파일입니다.

{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "deleteOutDir": true
  }
}
  • $schema: 파일의 구문과 구조를 정의하는 JSON 스키마의 URL을 지정합니다.
  • collection: 사용할 스키매틱 컬렉션의 이름을 지정합니다. @nestjs/schematics 컬렉션이 지정되어 있으며, NestJS 애플리케이션 및 모듈을 생성하는 스키매틱을 제공합니다
  • sourceRoot: 애플리케이션의 소스 코드 루트 디렉토리를 지정합니다.
  • compilerOptions: NestJS에서 사용하는 TypeScript 컴파일러의 옵션을 지정합니다. "deleteOutDir": true로 설정되어 있으며, 빌드하기 전에 컴파일된 코드의 출력 디렉토리가 삭제됩니다.

스키매틱(Schematic)은 프로젝트에서 자주 사용되는 코드, 구조, 파일 등의 패턴을 정의하고, 이를 템플릿화하여 새로운 프로젝트나 파일을 생성할 때 이러한 패턴을 쉽게 적용할 수 있도록 도와주는 도구입니다. 스키매틱은 프레임워크(Framework)와 유사하지만, 미리 정의된 템플릿을 사용하여 프로젝트를 생성하거나 수정하는 작업에 초점을 맞춘 도구입니다. 반면에 프레임워크는 애플리케이션의 전체적인 구조와 개발 방법을 정의하는 라이브러리와 도구들의 집합으로, 코드의 구조화와 표준화를 제공하여 개발자들이 보다 쉽게 애플리케이션을 개발할 수 있도록 도와줍니다.

 

package.json

프로젝트의 정보와 의존성 정보를 담고 있는 파일입니다.
npm이나 yarn 과 같은 패키지 매니저를 사용하여 의존성을 관리하는데 사용됩니다.

또한 스크립트를 등록하여 프로젝트에서 사용하는 명령어를 정의할 수 있는데요 이러한 스크립트는 npm run 명령어를 통해 실행할 수 있습니다.

Maven의 pom.xml 이나 Gradle의 build.gradle 과 같은 역활을 하는 파일이라고 할 수 있습니다.

{
  "name": "nestjs-project",
  "version": "0.0.1",
  "description": "",
  "author": "",
  "private": true,
  "license": "UNLICENSED",
  "scripts": {
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  },
  "dependencies": {
    "@nestjs/common": "^9.0.0",
    "@nestjs/core": "^9.0.0",
    "@nestjs/platform-express": "^9.0.0",
    "reflect-metadata": "^0.1.13",
    "rxjs": "^7.2.0"
  },
  "devDependencies": {
    "@nestjs/cli": "^9.0.0",
    "@nestjs/schematics": "^9.0.0",
    "@nestjs/testing": "^9.0.0",
    "@types/express": "^4.17.13",
    "@types/jest": "29.2.4",
    "@types/node": "18.11.18",
    "@types/supertest": "^2.0.11",
    "@typescript-eslint/eslint-plugin": "^5.0.0",
    "@typescript-eslint/parser": "^5.0.0",
    "eslint": "^8.0.1",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^4.0.0",
    "jest": "29.3.1",
    "prettier": "^2.3.2",
    "source-map-support": "^0.5.20",
    "supertest": "^6.1.3",
    "ts-jest": "29.0.3",
    "ts-loader": "^9.2.3",
    "ts-node": "^10.0.0",
    "tsconfig-paths": "4.1.1",
    "typescript": "^4.7.4"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }
}

생소한 Jest 프레임워크에 대한 설정만 살펴보겠습니다.

  • moduleFileExtensions: Jest에서 인식하는 파일 확장자 목록입니다. 이 설정을 사용하여 Jest가 어떤 파일을 테스트 파일로 인식할지 지정할 수 있습니다.
  • rootDir: Jest가 테스트 파일을 찾을 디렉토리입니다. 이 설정은 프로젝트 루트 디렉토리에서 상대적인 경로로 지정됩니다.
  • testRegex: Jest가 테스트 파일을 찾을 때 사용하는 정규식 패턴입니다.
  • transform: Jest가 테스트 파일을 처리하기 전에 사용하는 변환기입니다. 이 설정을 사용하여 TypeScript 파일을 JavaScript 파일로 변환하거나 Babel을 사용하여 ES6 코드를 ES5 코드로 변환할 수 있습니다.
  • collectCoverageFrom: 테스트 코드 커버리지를 수집할 파일 목록을 지정합니다.
  • coverageDirectory: 테스트 커버리지 보고서를 생성할 디렉토리입니다.
  • testEnvironment: Jest가 테스트 코드를 실행할 환경을 지정합니다. NestJS에서는 기본적으로 Node.js 환경을 사용합니다.

 

package-lock.json

npm이 모듈의 의존성 트리를 만드는 방식을 바탕으로 모듈 설치를 일관성 있게 유지하기 위한 파일입니다.
이 파일은 모듈의 의존성 트리를 생성하고 패키지 버전을 고정시켜, 다른 개발자나 빌드 시스템이 항상 동일한 모듈 버전을 설치할 수 있도록 합니다.

package-lock.json 파일은 npm 5 버전 이후부터는 자동으로 생성되며, 이전 버전의 npm에서는 npm install 명령어를 사용할 때 --save-exact 옵션을 사용하면 비슷한 효과를 얻을 수 있습니다.

 

tsconfig.json

TypeScript 컴파일러 설정 파일입니다.
TypeScript 컴파일러는 TypeScript 코드를 JavaScript 코드로 변환해주는 도구입니다. TypeScript는 JavaScript를 확장한 언어이기 때문에, TypeScript 컴파일러는 ECMAScript 명세에 따라 타입을 체크하여 JavaScript 코드를 생성합니다. 따라서 TypeScript는 모든 JavaScript 런타임에서 실행 가능하며, 최신 ECMAScript 버전에서도 동작합니다.

ECMAScript 란?
JavaScript 언어의 표준화된 버전을 의미합니다. ECMAScript 6 (ES6)부터는 새로운 기능들이 추가되면서 JavaScript의 생산성과 가독성이 크게 향상되었습니다. 지정된 es2017 버전은 ES2015(ES6)의 확장판으로 let과 const 선언문, 화살표 함수, 클래스 모듈 등이 추가되었다고 합니다.

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false
  }
}
  • module: 컴파일된 JavaScript 파일에서 사용할 모듈 시스템을 선택합니다. "commonjs"는 Node.js와 함께 사용하도록 선택한 것입니다.
  • declaration: .d.ts 파일을 생성할지 여부를 지정합니다. 이 파일은 TypeScript 코드에서 사용하는 인터페이스, 클래스, 함수 등의 선언을 JavaScript 코드에서 사용할 수 있게 해줍니다.
  • removeComments: 컴파일된 JavaScript 코드에서 주석을 제거할지 여부를 지정합니다.
  • emitDecoratorMetadata: 데코레이터를 사용하는 클래스나 속성에 대한 메타데이터를 생성할지 여부를 지정합니다.
  • experimentalDecorators: 실험적인 데코레이터를 사용할지 여부를 지정합니다.
  • allowSyntheticDefaultImports: 모듈에서 default export가 없는 경우 default import를 허용할지 여부를 지정합니다.
  • target: 컴파일된 JavaScript 코드가 대상으로 하는 ECMAScript 버전을 지정합니다.
  • sourceMap: 컴파일된 JavaScript 코드와 TypeScript 소스 코드 간의 매핑 정보를 생성할지 여부를 지정합니다.
  • outDir: 컴파일된 JavaScript 파일이 생성될 디렉토리를 지정합니다.
  • baseUrl: 모듈을 해석할 때 상대적인 기준 경로를 지정합니다.
  • incremental: 증분 컴파일을 사용할지 여부를 지정합니다.
  • skipLibCheck: TypeScript 라이브러리 파일(.d.ts)에서의 타입 체크를 건너뛸지 여부를 지정합니다.
  • strictNullChecks: null과 undefined를 명시적으로 처리하도록 강제할지 여부를 지정합니다.
  • noImplicitAny: any 타입을 사용할 때 명시적으로 타입을 지정하지 않았을 경우 컴파일 에러를 발생시킬지 여부를 지정합니다.
  • strictBindCallApply: this 바인딩과 함수 호출에 대한 엄격한 체크 여부를 지정합니다.
  • forceConsistentCasingInFileNames: 파일 이름이 대소문자를 구분하지 않도록 강제할지 여부를 지정합니다.
  • noFallthroughCasesInSwitch: switch 문에서의 fallthrough를 방지할지 여부를 지정합니다.

 

tsconfig.build.json

tsconfig.json 파일을 확장하여 사용하는 파일로 빌드 시 필요한 설정들, 빌드할 때 필요 없는 파일들을 명시합니다.

{
  "extends": "./tsconfig.json",
  "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

 

main.ts

NestJS 어플리케이션을 부트스트랩하는 역할을 합니다.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

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

NestFactory 모듈을 import해서 애플리케이션을 생성하고, AppModule을 전달합니다. AppModule은 NestJS에서 가장 중요한 모듈 중 하나로, NestJS의 Dependency Injection 컨테이너에서 모듈을 로드하고 구성하는 역할을 합니다.
app.listen 함수를 사용해서 애플리케이션을 시작합니다. 이 함수는 인자로 포트 번호를 받아들이며, 해당 포트 번호로 HTTP 서버가 실행됩니다.

부트스트랩(bootstrap)이란, 프로그램 실행 시 초기화 작업을 수행하는 과정을 의미합니다. 일반적으로 컴퓨터 시스템의 부팅 과정을 뜻하지만, 소프트웨어에서도 비슷한 개념으로 사용됩니다. 보통 애플리케이션의 시작점에서 초기화 작업을 수행하는 것을 의미하며, 이를 통해 필요한 리소스를 미리 할당하거나 설정을 초기화할 수 있습니다.스프링 부트에서는 @SpringBootApplication 어노테이션이 붙은 클래스가 부트스트랩의 역할을 하고 있습니다.

 

app.module.ts

NestJS 애플리케이션에서 사용되는 모듈을 정의하는 파일입니다.
NestJS Module 이란?

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

@Module() 은 어노테이션이 아니라 데코레이터로 불립니다.

Spring Framework의 어노테이션과 NestJS의 데코레이터

NestJS의 데코레이터와 Spring Framework의 어노테이션은 기본적으로 유사한 개념입니다. 둘 다 클래스, 메서드, 변수 등에 메타데이터를 부여하여 애플리케이션을 구성할 수 있도록 지원합니다.

 

app.controller.ts

NestJS 에서 Controller 역할을 하는 AppController 클래스입니다.
[NestJS Controller에 대해 알아보기]

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

 

app.controller.spec.ts

app.controller.spec.ts 파일은 app.controller.ts 의 유닛 테스트를 위한 파일이다.

import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';

// 'AppController'에 대한 테스트 스위트를 정의
describe('AppController', () => {
  let appController: AppController;
  let mockAppService: Partial<AppService>; // AppService의 Mock 객체

  // 각 테스트 케이스 실행 전에 실행되는 설정
  beforeEach(async () => {
  	mockAppService = {
    	getHello: jest.fn(() => 'Hello World!'), // getHello 메서드를 Mock 함수로 설정
    };
  
    // 테스트 모듈을 생성하고 컴파일
    const app: TestingModule = await Test.createTestingModule({ 
      controllers: [AppController],       // 테스트에 사용할 컨트롤러를 정의
      providers: [
      	{ provide: AppService, useValue: mockAppService },  // mockAppService 사용
      ],
    }).compile();

    // 테스트 모듈에서 'AppController'의 인스턴스를 가져와서 변수에 할당
    appController = app.get<AppController>(AppController);
  });

  // 'root' 메서드에 대한 테스트 케이스를 정의
  describe('root', () => {
    it('should return "Hello World!"', () => {
      expect(appController.getHello()).toBe('Hello World!'); // AppController의 getHello 메서드를 호출하여 반환값을 확인
      expect(mockAppService.getHello).toHaveBeenCalled();    // AppService의 getHello 메서드가 호출되었는지 확인
    });
  });
});
테스트 스위트는 테스트 케이스들의 집합이다. 보통 describe 함수를 사용하여 스위트를 정의하고, 그 안에 여러 개의 it 함수를 사용하여 개별적인 테스트 케이스를 작성한다. 이렇게 테스트 스위트를 구성하면 테스트 케이스들을 조직화하고, 실행하거나 제외하기 쉽게 되어 효율적으로 테스트를 수행할 수 있습니다.

 

app.e2e-spec.ts

NestJS 애플리케이션의 end-to-end(e2e) 테스트를 위한 파일입니다. 이 파일은 @nestjs/testing 패키지에서 제공하는 Test 클래스와 INestApplication 인터페이스 등을 사용하여 애플리케이션의 테스트를 작성합니다.

describe()로 테스트 스위트를 정의하고, beforeEach() 로 각 테스트 전에 필요한 모듈을 불러와 테스트 환경을 설정합니다. 그리고 it() 로 실제 테스트를 작성합니다.

import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';

describe('AppController (e2e)', () => {
  let app: INestApplication;

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

  it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(200)
      .expect('Hello World!');
  });
});

@nestjs/testing 패키지에서 제공하는 Test 와 TestingModule 을 사용하여 AppModule 을 import 하여 테스트 환경을 구성합니다. beforeEach 블록에서는 TestingModule 으로부터 INestApplication 을 생성하고 init() 를 호출하여 애플리케이션을 초기화합니다.

it() 블록에서는 supertest 라이브러리를 사용하여 HTTP 요청을 보내고, HTTP 응답이 기대한 대로 반환되는지 확인합니다. 예시에서는 / 엔드포인트에 대한 GET 요청을 보내고, 응답 코드가 200이며 응답 본문이 Hello World! 인지를 검증합니다.

이렇게 작성된 e2e 테스트는 HTTP 요청을 보내고 받는 완전한 애플리케이션 실행 환경에서 테스트를 수행하며, 실제 운영환경에서 발생할 수 있는 다양한 문제를 재현하고 발견할 수 있습니다.

 

jest-e2e.json

Jest 테스트 프레임워크에서 End-to-End (e2e) 테스트를 수행할 때 사용되는 설정 파일입니다.

{
  "moduleFileExtensions": ["js", "json", "ts"],
  "rootDir": ".",
  "testEnvironment": "node",
  "testRegex": ".e2e-spec.ts$",
  "transform": {
    "^.+\\.(t|j)s$": "ts-jest"
  }
}
  • moduleFileExtensions: 테스트 코드에서 사용되는 모듈의 확장자를 지정합니다.
  • rootDir: Jest가 테스트를 수행할 디렉토리를 지정합니다.
  • testEnvironment: Jest가 테스트를 실행할 환경을 지정합니다. 이 경우 Node.js 환경에서 실행됩니다.
  • testRegex: 테스트 파일의 이름 패턴을 지정합니다. .e2e-spec.ts로 끝나는 파일만 테스트 대상으로 설정됩니다.
  • transform: Jest가 테스트 파일을 해석할 때 사용할 변환기를 설정합니다. 이 경우 ts와 js파일을 TypeScript로 변환합니다.

 

NestApplication 실행하기

지금까지 NestJS 프로젝트 생성시 기본으로 생성되는 파일들을 살펴보았습니다. 이제 잘 작동하는지 확인해 봐야겠지요??

package.json 파일에서 start 명령어를 확인합니다. npm run start명령어로 어플리케이션을 실행 후 main.ts 파일에서 지정한 3000번 포트로 접속을 해주면 문제없이 애플리케이션이 구동되었습니다.