Develop/NestJS

NestJS - Nomad Coders(1)

Judaeng 2022. 3. 8. 20:00

NestJS

GitRepo

NestJS로 API 만들기 강의를 보고 정리했다.

블로그 참고

 

✏️ NestJS란? (Express/Fastify)

- NodeJS 백엔드를 만들기 위한 프레임워크

- TypeScript 기반 (TypeScript -> NestJS 순서로 공부 추천)

- JS에 TS가 있다면, NodeJS에는 NestJS가 있다.

- Python, Java, Ruby 언어들의 프레임워크의 장점의 특징을 NestJS는 가지고 있다.

- 규칙과 패턴, 구조 등이 있어서 안정감을 느낄 수 있고, 테스트 툴을 제공한다.

- OOP(객체 지향 프로그래밍), FP(기능 프로그래밍), FRP(기능 반응 프로그래밍)의 요소를 지원

- 내부적으로 NestJS는 Express(기본 값)와 같은 강력한 HTTP 서버 프레임워크를 사용하며, 선택적으로 Fastify도 사용할 수 있도록 구성할 수 있다. 

- NestJS는 효율적이고, 안정적이며, 확장에 용이한 서버 애플리케이션을 구축하기 위한 진보된 NodeJS 프레임워크

더보기

NestJS는 Express와 같은 서버 프레임워크이지만, Express는 하고 싶은 대로 아무거나 할 수가 있다.
프로젝트를 만들고 싶으면 룰도 따를 필요없다.
미들웨어를 어디에다 둘지, 시스템 디자인을 어떻게 하든 자유이다.

하지만 NestJS는 그럴 수가 없다.
구조가 있기 때문에 시스템 디자인 측면에서 지원하고, 순서와 규칙도 있어서 이를 잘 따르기만 한다면 큰 규모의 백엔드를 쉽게 만들 수 있다고 한다.

 

✏️ NestJS 장점

- 효율성: NestJS는 typescript의 적극적인 도입, DI(Dependency Injection), IoC(Inversion of Control), Module을 통한 구조화 등의 기술을 통해 생산적인 개발이 용이하다.

- 안정적: NestJS는 typescript를 적극적으로 도입함으로써 서버 애플리케이션 개발 시 발생할 수 있는 오류들을 사전에 방지할 수 있도록 하였다.
모듈로 감싸는 형태로 개발하기 때문에 모듈 별로 테스트 코드를 쉽게 작성할 수 있도록 구현되어 있다.

- 확장성: NestJS는 module을 통해 확장이 용이하도록 설계되어 있다.
실제로 사용해보면 module을 통해 코드적으로, 논리적으로 구분한다는 장점을 크게 느끼실 수 있다.
또한 NestJS는 기본적으로 마이크로 서비스 아키텍처 개발 스타일을 제공한다.

 

✏️ NestJS 설치(Hello World)

프로젝트를 새로 생성하는 형식으로, 아래 명령어 입력이 필요하다.

// NestJS 설치하기
$ npm i -g @nestjs/cli

// NestJS 프로젝트 만드는 방법
$ nest new project-name

// 커맨드 리스트 보이면 설치 완료
$ nest 

// nest 서버 시작(dev는 -W 옵션이 들어있음)
$ npm run start:dev

위에 명령어를 차근차근 입력하고 만들어진 프로젝트의 서버를 시작하면, "Hello World"가 localhost:3000에 뜬 것을 확인할 수가 있다.

 

✏️ 구조와 데코레이터

main.ts이라는 이름은 변경하면 안 된다고 한다.

// 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: [],
  // AppController 정의된 곳으로 이동 (express router 역할)
  controllers: [AppController], 
  providers: [AppService],
})
export class AppModule {}

데코레이터는 클래스에 함수 기능을 추가할 수가 있다.

 

AppController 정의된 곳으로 가면 @Get 데코레이터를 확인할 수 있다.

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

@Controller()
export class AppController {
  // AppService 정의된 곳으로 이동
  constructor(private readonly appService: AppService) {}

  @Get() // 데코레이터와
  getHello(): string { // 함수는 항상 붙어있어야 함
    return this.appService.getHello();
  }
}

 

@Get -> @Post 수정 후에 localhost:3000 확인하면 아래처럼 메시지를 볼 수가 있다.

출처: https://velog.io/@flobeeee/TIL-NestJS

// app.service.ts
import { Injectable } from '@nestjs/common';

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

 

✏️ Controller 생성

NestJS CLI는 명령어를 사용해서 Module , Controller, Service 등을 만들 수가 있다.

이렇게 만들어지면 Module에 controllers, providers 등에서 자동으로 환경을 맞춰주고 파일을 생성해준다.

// nest cli 명령어, generate 명령어, 단축키 리스트를 보여줌
$ nest --help

// nest generate controller 명령어 (컨트롤러 만들기)
$ nest g co

위에 명령어로 만들어지면 모듈 부분은 아래처럼 변한다.

물론 Movies는 만들고 싶은 이름이었다.

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

@Module({
  imports: [],
  controllers: [MoviesController],
  providers: [],
})
export class AppModule {}
// movie.controller.ts
@Controller('movies') // endPoint
export class MoviesController {
  constructor(private readonly moviesService: MoviesService) {}

  @Get() // http://localhost:3000/movies 로 접근가능
  getAll(): Movie[] {
    return this.moviesService.getAll();
  }
}

 

✏️ Query, Param, Body 받아오기

Express에선 data들이 req에 담겨있었지만, NestJS에서는 정보가 필요하면 요청해야 한다.

Express에서 Body를 JSON으로 리턴하려면 따로 설정해야 했지만, NestJS는 알아서 JSON으로 들어온다.

컨트롤러는 URL을 매핑하고 req를 받아주는 역할, 서비스는 로직을 관리할 예정이다.

// movie.controller.ts

@Get('search') // http://localhost:3000/movies/search?year=2000
  getOne(@Query('year') searchingYear: string): Movie {
    return `we are searching for a movie made after ${searchingYear}`;
    // 우선 이렇게 해서 값을 받아오는지 확인 후에 service 파일 작성하면 좋음.
  }

@Get(':id') // http://localhost:3000/movies/1
  getOne(@Param('id') movieId: string): Movie {
    return `This will return one movie with the id ${movieId}`;
  }

@Post()
  create(@Body() movieData) {
    return movieData
  }

Query

Query를 가져오는 방법은 Param을 가져오는 방법과 동일하다.

Front에서 보내는 Query 변수를 Query 어노테이션 안에 넣어주면 된다.

 

Param

Get요청에서 /:id를 통해 들어오는 id를 가져오는 방법이다.

nest에서는 get요청을 할 때는 Get 어노테이션을 사용하면 된다.

또한 Get 어노테이션 안에 Routing 되는 주소도 입력해줘야 한다.

Params를 가져올 때는 Param 어노테이션을 사용하여 가져온다.

 

Body

nest는 express를 동시 지원하기 때문에 req, res를 사용할 수 있다.

하지만 nest에서는 권장하지 않는다고 한다. (req, res를 사용하면 내부적으로 속도가 많이 느려진다고 한다)

nest의 req는 위에서 알아본 대로 Body 어노테이션을 이용하여 받고 res는 컨트롤러에서 return 시킨 값을 프런트로 보내주게 된다. -> DB의 값을 파싱 하여 응답 값을 service에서 그냥 리턴해도 값이 전달된다.

 

가짜 데이터베이스 만들기

// src/movies/entities/movie.entity.ts
// 인터페이스 movie 구성
// 실제로는 여기에 DB 모델을 둔다.
export class Movie {
  id: number;
  title: string;
  year: number;
  genres: string[];
}

서비스 로직

// src/movies/movie.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { Movie } from './entities/movie.entity';

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

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

  getOne(id: number): Movie {
    const movie = this.movies.find((movie) => movie.id === id);
    if (!movie) {
      // nest 에 내장되어있는 에러처리
      throw new NotFoundException(`Movie with ID ${id} not found`);
    }
    return movie;
  }
}

에러 처리 부분 insomnia tool을 이용해서 확인한다.

출처: https://velog.io/@flobeeee/TIL-NestJS
출처: https://velog.io/@flobeeee/TIL-NestJS

 

✏️ 유효성 검사

// src/movies/dto/create-movie.dto.ts
import { IsNumber, IsOptional, IsString } from 'class-validator';

export class CreateMovieDto {
  @IsString()
  readonly title: string;

  @IsNumber()
  readonly year: number;

  @IsOptional() // 유효성검사 무시 (선택적인 인자)
  @IsString({ each: true }) // 요소 각각 검사한다는 뜻
  readonly genres: string[];
}
// movie.service.ts
import { CreateMovieDto } from './dto/create-movie.dto';

  create(movieData: CreateMovieDto) { // 유효성 검사
    this.movies.push({
      id: this.movies.length + 1,
      ...movieData,
    });
  }

 

main.ts useGlobalPipes ValidationPipe 옵션
// main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(
    new ValidationPipe({ // express 의 middleware 역할
      whitelist: true, // 인자로 받는 것만 들어오게 할 수 있음
      forbidNonWhitelisted: true, // 메세지로 해당 인자는 사용할 수 없다고 안내함
      transform: true, // 우리가 원하는 타입으로 변환해줌
    }),
  );
  await app.listen(3000);
}
bootstrap();

옵션 적용 후, 받지 않는 인자로 요청하는 경우 에러 메시지

출처: https://velog.io/@flobeeee/TIL-NestJS

 

✏️ Express on NestJS

바디나 쿼리 파라미터를 요청하는 대신에 req를 받는 방법도 있다.
하지만 비추천한다. -> 공식 문서에 방법이 있다.
NestJS는 express 위에서 실행되지만 다른 프레임워크인 Fastify로 전환할 수도 있다.
이 경우 성능이 2배로 향상된다.
express에 접근하는 방식이 아닌 NestJS 방식으로만 쓰면 전환이 쉽다.

 

🤔 블로그 정리 후, 느낀 점

처음엔 Typescript를 공부하면서 아무 생각이 없었다.

최근에 JS로 카카오 챗봇을 만들고 있었는데, 이것을 TS로 리팩터링 하는 과정에서 NestJS를 알게 된 것 같다.

생각보다 재밌고, 프레임워크가 너무 좋아서 재밌게 강의를 들었다. 가능하다면 직접 해보는 것이 제일 좋다.

강의를 듣고 난 후에, 난 챗봇을 NestJS로 리팩터링 해보고, NestJS를 추가로 공부하는 중이다.

추가로 올릴 수 있으면 NestJS의 Controller, Service, Module의 흐름, 그것이 자세하게 무엇인지 정리하고 싶다.

그리고 공식문서로 공부하는 습관을 또 까먹은 것 같아서 다시 글을 적으면서 정신 차려야 될 것 같다😥

 

강의 내용도 너무 좋고, 위에 글에 다 담을 수가 없어서 몇 가지 정리를 더 해본다.

- NotFoundException():  NestJS에서 지원해주는 내부 함수라고 들었다.

- DTO(Data Transfer Object): 사람들이 보낼 수 있는 것, 보냈으면 하는 것을 만든다고 했다.

- useGlobalPipes(): 파이프를 만드는데, 사용자들이 보낸 코드가 이 파이프 안에 들어와 유효성 검사 파이프로 들어가게 된다.

- ValidationPipe(): 사용자들이 보낸 요청에 코드의 유효성을 체크해준다.

   - whitelist -> true로 설정하면 아무 decorator도 없는 어떠한 property의 object를 거른다, 인자로 받는 것만 들어오게 할 수 있다.

   - forbidNonWhitelisted -> 한 단계 보안을 더 업그레이드, true라면 누군가가 이상한 걸 보내면 요청 자체를 막아버릴 수 있다. 메시지로 해당 인자는 사용할 수 없다고 안내해줌

   - transform -> 기본적으로 true를 주면 유저들이 보낸 값을 우리가 원하는 실제 타입으로 변환해준다. 

   ex) 사용자 요청 "안녕" -> number type으로 변환 -> 우리가 서버 안에서 원하는 값, 타입으로 변환해줌

- IsOptional(): 유효성 검사 무시(선택적인 인자)

 

 

📝이번 게시물을 만들기 위해 참고한 사이트

1. NestJS 공식문서