본문 바로가기

Web Dev/Javascript

Nest.js 는 Node.js 의 프레임워크 오케이 ? RestAPI 만들어 보자

반응형

https://nestjs.com/

 

NestJS - A progressive Node.js framework

NestJS is a framework for building efficient, scalable Node.js web applications. It uses modern JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Progr

nestjs.com

우선 

설치 할 것들이 여러가지가 있는데 

구글 검색 창에 insomnia rest를 검색해보자 

 

공짜 버전으로 회원가입을 진행해준다. 

내가 생각하기에는 그냥 Postman 과 유사한 API 테스트 프로그램이라고 생각한다.

설치가 완료 되면 

이와 같은 파일 리스트를 볼 수 있고 

기본 세팅 언어는 TypeScript 입니다. 

이와 같이 MVC 모델링을 제공하는 것 같네요 

다음으로 Package.json 파일을 확인합니다. 

{
  "name": "hi-nest",
  "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": "^10.0.0",
    "@nestjs/core": "^10.0.0",
    "@nestjs/platform-express": "^10.0.0",
    "reflect-metadata": "^0.2.0",
    "rxjs": "^7.8.1"
  },
  "devDependencies": {
    "@nestjs/cli": "^10.0.0",
    "@nestjs/schematics": "^10.0.0",
    "@nestjs/testing": "^10.0.0",
    "@types/express": "^4.17.17",
    "@types/jest": "^29.5.2",
    "@types/node": "^20.3.1",
    "@types/supertest": "^6.0.0",
    "@typescript-eslint/eslint-plugin": "^6.0.0",
    "@typescript-eslint/parser": "^6.0.0",
    "eslint": "^8.42.0",
    "eslint-config-prettier": "^9.0.0",
    "eslint-plugin-prettier": "^5.0.0",
    "jest": "^29.5.0",
    "prettier": "^3.0.0",
    "source-map-support": "^0.5.21",
    "supertest": "^6.3.3",
    "ts-jest": "^29.1.0",
    "ts-loader": "^9.4.3",
    "ts-node": "^10.9.1",
    "tsconfig-paths": "^4.2.0",
    "typescript": "^5.1.3"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }
}

 

npm run start:dev

해당 명령어로 프로젝트를 실행해 봅시다. 

실행 완료 ! 

Nest.js 에는 main.ts 가 있는데 이 파일은 절대로 해당 이름 그대로 유지 되어야 한다. 

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

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

내부는 그냥 간단한 서버에 올리는 소스 코드 정도 ? 

app.module.ts 

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 -> 데코레이터라고 부른다. 

클래스에 함수 기능을 추가할 수 있다 - > Java Bean ?  같은건가 ? 

app.controller.ts 

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();
  }
}

해당 소스가 있다. 

getHello 함수를 따라 appService로 가면 

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

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

여기서  getHello 함수가 호출 되어 웹 페이지에 반영된다. 

 

여기서 Controller 는 -> node.js 의 라우터 같은 존재 ? 

url 을 가져오고 함수를 실행하는 정도

하나 테스트 해보자 

 @Get('/hello')
  sayHello(): string {
    return this.appService.sayHello();
  }

요렇게 코드를 추가하고 물론 appService 에도 함수를 등록하면

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

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
  sayHello(): string {
    return 'fuck you!!';
  }
}

url 타고 잘 들어가 질까? 

잘 된다. 

코드를 체계적으로 구체화 하고 이해하기 쉽게 만들어주는 프레임워크 ! 

라우터를 따로 세팅 안해줘도 된다. 

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

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

  @Get('/hello')
  sayHello(): string {
    return this.appService.sayHello();
  }
  @Post('/hello')
  byeBye(): string {
    return this.appService.byeBye();
  }
}

그렇다면 그냥 return 에다가 함수를 작성하면되는데 왜 service가 필요할까 ? 

Nest.js는 Node.js 환경에서 서버 사이드 애플리케이션을 구축하기 위한 프레임워크입니다. Nest.js의 아키텍처는 모듈성, 확장성, 유지보수성을 강조하며, 다음과 같은 주요 컴포넌트로 구성됩니다.

모듈 (Modules):
Nest.js 애플리케이션은 모듈로 구성됩니다.
모듈은 관련된 기능을 캡슐화하고 구성하는 데 사용됩니다.
각 모듈은 컨트롤러, 프로바이더, 서비스 등을 포함할 수 있습니다.
모듈은 @Module 데코레이터를 사용하여 정의됩니다.
컨트롤러 (Controllers):
컨트롤러는 들어오는 HTTP 요청을 처리하고 응답을 반환하는 역할을 합니다.
@Controller 데코레이터를 사용하여 컨트롤러를 정의합니다.
컨트롤러는 라우트 핸들러 메서드를 포함하며, HTTP 메서드에 해당하는 데코레이터(@Get, @Post 등)를 사용하여 정의합니다.
프로바이더와 서비스 (Providers and Services):
프로바이더는 Nest.js의 종속성 주입 시스템에서 사용되는 객체입니다.
서비스는 애플리케이션의 비즈니스 로직을 캡슐화하고 재사용 가능한 코드를 제공하는 프로바이더의 한 유형입니다.
@Injectable 데코레이터를 사용하여 프로바이더와 서비스를 정의합니다.
프로바이더와 서비스는 종속성 주입을 통해 다른 컴포넌트에 주입될 수 있습니다.
종속성 주입 (Dependency Injection):
Nest.js는 강력한 종속성 주입 시스템을 제공합니다.
종속성 주입을 통해 객체 간의 느슨한 결합을 유지하고 코드의 모듈성과 테스트 가능성을 향상시킵니다.
생성자 주입, 속성 주입, 메서드 주입 등의 방법을 지원합니다.
미들웨어 (Middleware):
미들웨어는 요청과 응답 사이에 위치하여 추가적인 처리를 수행하는 함수입니다.
Nest.js는 Express.js 스타일의 미들웨어를 지원합니다.
미들웨어는 요청 검증, 인증, 로깅 등의 공통 기능을 구현하는 데 사용될 수 있습니다.
예외 필터 (Exception Filters):
예외 필터는 애플리케이션에서 발생하는 예외를 처리하고 적절한 응답을 반환하는 역할을 합니다.
@Catch 데코레이터를 사용하여 예외 필터를 정의합니다.
예외 필터는 특정 예외 클래스나 HTTP 상태 코드에 따라 예외를 처리할 수 있습니다.
파이프 (Pipes):
파이프는 요청 데이터의 유효성 검사, 변환, 필터링 등을 수행하는 데 사용됩니다.
@UsePipes 데코레이터를 사용하여 파이프를 적용할 수 있습니다.
Nest.js는 기본 제공되는 파이프와 사용자 정의 파이프를 지원합니다.
가드 (Guards):
가드는 라우트 핸들러 메서드 실행 전에 인증, 권한 부여 등의 조건을 검사하는 데 사용됩니다.
@UseGuards 데코레이터를 사용하여 가드를 적용할 수 있습니다.
가드는 true/false를 반환하여 요청의 진행 여부를 결정합니다.
인터셉터 (Interceptors):
인터셉터는 요청과 응답을 가로채서 추가적인 처리를 수행하는 역할을 합니다.
@UseInterceptors 데코레이터를 사용하여 인터셉터를 적용할 수 있습니다.
인터셉터는 요청/응답 데이터의 변환, 로깅, 캐싱 등의 기능을 구현하는 데 사용될 수 있습니다.
이러한 컴포넌트들을 조합하여 Nest.js 애플리케이션을 구축할 수 있습니다. Nest.js의 아키텍처는 모듈성과 재사용성을 강조하며, 코드의 구조화와 유지보수성을 향상시킵니다. 또한, TypeScript와 데코레이터를 적극 활용하여 개발 생산성을 높이고 코드의 가독성을 향상시킵니다.

 

               +-----------+
               |  Module   |
               +-----------+
               |           |
       +-------+           +-------+
       |                           |
+------+------+             +------+------+
|  Controller |             |   Service   |
+-------------+             +-------------+
|             |             |             |
|  - Route    |             |  - Business |
|  - Handler  |             |    Logic    |
|             |             |             |
+-------------+             +-------------+
       |                           |
       |                           |
+------+------+             +------+------+
| Middleware  |             |  Repository |
+-------------+             +-------------+
|             |             |             |
|  - Request  |             |  - Data     |
|  - Response |             |    Access   |
|             |             |             |
+-------------+             +-------------+

 다이어그램은 Nest.js 아키텍처의 주요 구성 요소와 그들 간의 관계를 보여줍니다.

Module: 애플리케이션의 기능을 캡슐화하고 구성하는 역할을 합니다.
Controller: HTTP 요청을 처리하고 응답을 반환합니다.
Service: 비즈니스 로직을 구현하고 데이터 액세스를 담당합니다.
Middleware: 요청과 응답 사이에서 추가적인 처리를 수행합니다.
Repository: 데이터베이스와의 상호작용을 추상화하고 데이터 액세스 로직을 캡슐화합니다.
이 다이어그램은 Nest.js 아키텍처의 기본 구조를 나타내며, 실제 애플리케이션에서는 더 많은 컴포넌트와 레이어가 포함될 수 있습니다.

터미널에 nest 라는 명령어를 치면 

해당 명령어를 통해 쉽게 원하는 컴포넌트를 만들 수 있다 . 

예를 들어 nest g co 

바로 생성된다. 

module 파일에도 자동으로 등록 된다.

import { Module } from '@nestjs/common';
import { MoviesController } from './movies/movies.controller';

@Module({
  imports: [],
  controllers: [MoviesController],
  providers: [],
})
export class AppModule {}
import { Controller, Get } from '@nestjs/common';

@Controller('movies')
export class MoviesController {
  @Get()
  getAll(): string {
    return 'this will return all movies';
  }
}

이렇게 컨트롤러에 api 를 작성하면 

과연 어떻게 될까?

이렇게 밖에 안나오는데 ... 

이게 왜 이런고 하니 

Controller에 특정 이름을 붙여주면 그게 url 로 적용이된다. 

라익 디스

요부분이 우리 url 의 Entry Point( 엔트리 포인트)를 컨트롤한다는 거지 

api

api 테스터로 확인 해보자 

console 에 잘 찍히는 걸 볼 수 있다. 

Patch 도 좀 더 수정해보자 

  @Patch('/:id')
  patch(@Param('id') movieId: string, @Body() movieData: any) {
    return {
      updatedMovie: movieId,
      ...movieData,
    };
  }

Patch 부분도 잘 정상작동된다.

보면 express.js 였다면 JSON 을 주고 받을 때 형변환? 

코드 적으로 세팅을 해줬어야 한다면

Nest.js 에서는 그냥 보내면 알아서 받는다. 

너무 편하다  

queryParam 을 보내는 API 를 하나 만들어 보자 

  // express.js or nest.js 둘다 get : id 보다 다른 get 이 위에 있어야 한다.  
  @Get('search')
  search(@Query("year") searchingYear:string) {
    return `We are searching for a movie made after : ${searchingYear}`;
  }

id 보다 아래에 위치하면 

search가 id로 오인하는 경우가 있다. 

다시 api test 

query 인자가 잘 들어온다. 

 

이번에는 nest 명령어로 service도 자동으로 생성해보자 

아마 nest g s 이 명령어겠지 ? 

계속 terminal 통해서 

이렇게 친절하게 뭐가 만들어졌는지 잘 나온다. 

import { Module } from '@nestjs/common';
import { MoviesController } from './movies/movies.controller';
import { MoviesService } from './movies/movies.service';

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

providers 에 자동으로 Service 가 등록이 된다.

해당 service에서는 주로 데이터 베이스와 연동하여 Query 문을 처리했을 것이다.

우선은 entity 를 가라로 작성해보자 

 

 movie.entity.ts 

export class Movie {
  id: number;
  title: string;
  year: number;
  genres: string[];
}

nest.js 의 규칙성을 바탕으로 이름을 작성한다. 

 그리고 Service에 함수를 작성해준다. 

import { Injectable } from '@nestjs/common';
import { Movie } from './entities/movie.entity';

@Injectable()
export class MoviesService {
  private movies: Movie[] = [];

  getAll(): Movie[] {
    return this.movies;
  }

  getOne(id: string): Movie {
    return this.movies.find((movie) => movie.id === +id);
  }

  deleteOne(id: string): boolean {
    this.movies.filter((movie) => movie.id !== +id);
    return true;
  }

  create(movieData) {
    this.movies.push({
      id: this.movies.length + 1,
      ...movieData,
    });
  }
}

Controller 

import {
  Controller,
  Get,
  Param,
  Post,
  Delete,
  Patch,
  Body,
  Query,
} from '@nestjs/common';
import { MoviesService } from './movies.service';
import { Movie } from './entities/movie.entity';

@Controller('movies')
export class MoviesController {

//////////////////////////////
// constructor 사용 //
  constructor(private readonly moviesService: MoviesService) {}

  @Get()
  getAll(): Movie[] {
    return this.moviesService.getAll();
  }

  // express.js or nest.js 둘다 get : id 보다 다른 get 이 위에 있어야 한다.
  @Get('search')
  search(@Query('year') searchingYear: string) {
    return `We are searching for a movie made after : ${searchingYear}`;
  }

  @Get('/:id')
  getOne(@Param('id') movieId: string): Movie {
    return this.moviesService.getOne(movieId);
  }

  @Post()
  create(@Body() movieData: any) {
    return this.moviesService.create(movieData);
  }

  @Delete('/:id')
  remove(@Param('id') movieId: string) {
    return this.moviesService.deleteOne(movieId);
  }

  @Patch('/:id')
  patch(@Param('id') movieId: string, @Body() movieData: any) {
    return {
      updatedMovie: movieId,
      ...movieData,
    };
  }
}

이와 같이 작성해주고 다시 테스트 해본다. 

반응형