NestJS | 노마드 코더 : NestJS로 API 만들기 (이론)

Stellar·2021년 7월 26일
0

JS Runtime

목록 보기
4/13
post-thumbnail
  • 런타임 : 언어가 실행될 수 있는 환경.(JS는 브라우저에서만 사용이 가능하기 때문에 NodeJS같은 런타임을 이용해 브라우저 외에도 사용가능하게 만들어준다.)
  • 프레임워크 : 완성된 제품이 아닌 완성된 제품을 만들기 위해서 개발자를 도와주는 역할 (스프링, 장고, 노드 등)
  • 라이브러리 : 특정 기능에 대한 도구 또는 함수들의 집합 (jquery 등)
  • 차이점 : 둘의 차이점은 라이브러리는 내가 필요한 경우 가져다 쓰고 프레임 워크는 정해진 규칙이 있어 우리가 따라야함
  • 프레임 워크는 집! 라이브러리는 가구!
  • 리액트가 애매하게 내가 필요한 경우 불러올 수 있어서 라이브러리기도한데 동시에 컴포넌트를 불러와서 프레임워크라고도 불림

그럼 왜 Node.js는 백엔드 영역이라는 오해가 생겼을까?
Node.js를 통해서 서버도 만들 수 있기 때문이다.

https://backback.tistory.com/383 - nestjs 자세한 설명


0.1 Welcome

  • Node.js의 프레임 워크
  • express 위에서도 움직이며 express를 이용함.
  • express란 모든 node.js 어플리케이션의 초석
  • 규칙과 구조가 존재함 ( 초보입장에서는 좋아보임)

0.2 Requirements

  • Rest API 만들기
  • Node.js를 사용해본 적이 있어야함.
  • insommia == Rest 클라이언트 == postman

0.3 Project Setup

  • nest 프로젝트 생성

1.0 Overview

  • ./src ~spec.ts 파일 삭제
  • $ npm run start:dev (NestJS 시작)
  • 브라우저에서 localhost:3000 을 치면 'Hello World!'가 출력!
    • $ npm run start:dev 터미널창에 켜놔야 브라우저에서 작동함
  • NestJS는 데코레이터를 자주 사용.
    데코레이터는 클래스 위의 함수이고 클래스를 위해 움직인다.

1.1 Controllers

  • main.ts == 장고 setting.py와 같다.
  • Controller는 url을 가져오고 함수를 실행한다.
  • 데코레이터 == (express에서의) 라우터
  • 데코레이터는 꾸며주는 함수나 클래스와 붙어있어야함.
  • 데코레이터에 url을 지정해줌 (왜지?) : @Get(url)을 지정하므로써 사용자가 path를 타고 다닐 수 있기 때문이다.
@Get('/hello')
    sayHello(): string {
      return 'Hello everyone';
    }
  • main.ts 파일에서 AppModule을 불러와 실행시키는데 AppModule은 전체 파일을 대표하여 실행된다.

  • this는 클래스 내부에 있는 함수를 가져와 사용하게 해준다.
    따라서 아래 코드와 함께 보면 constructor로 AppService 클래스를 불러오고 그것을 아래 getHello함수에서 appService.getHello 함수를 불러 올 수 있게 해준다.

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(); // AppService 클래스의 getHello 함수를 불러온다.
  }

1.2 Services

  • 기존 파일 삭제 후 새로 만들기위한 작업
  • Nest의 경우 컨트롤러와 서비스를 분리해 비즈니스 로직은 서비스로, 컨트롤러는 그냥 url을 가져오는 역할로 구분함.
  • app.module.ts파일에 모든 파일을 import해줘야 main.ts파일에서 읽을 수 있으므로 새로운 파일들은 import해줄 것.

2.0 Movies Controller

  • hostman으로 불러오는게 편함.
  • url 가져오고, function 실행 파일 만들기
$ nest
$ nest g co // nest 입력 후 나오는 표에서 controller에 맞는 단축어는 'co'이므로
// g 뒤에 입력, g는 nest에서 보여주는 표에 적힌 문법임
// controller 파일 생성
  • 쿼리 파라미터
//hostman - localhost:3000/movies/1
@Get('/:id')
    getOne(){
      return 'This will return one movie';
    }
  • 선 요청 후 응답
  • 메소드
@Get() //전체를 가져옴
    getAll() {
      return '';
    }

@Get('/:id') //하나만 가져옴
    getOne(@Param('id') id(변수명): string(타입)) {
      return `${id}`; //템플릿 리터럴 ${}은 작은따옴표(')나 큰따옴표(") 대신 백틱(`)(grave accent)로 감싼다.
    }

@Post()
    create(){
      return 'This will create a movie';
    }

@Delete('/:id')
    remove(@Param('id') movieId: string) {
      return `This will delete a movie with the id: ${movieId}`;
    }

// @Put() //전체 수정

@Patch('/:id') //부분 수정
    patch(@Param('id') movieId: string) {
      return `This will patch a movie with the id: ${movieId}`;
    }

2.1 More Routes

  • 브라우저에서 받은 데이터를 BODY에 담아 JSON을 통해 백으로 전달
//body(JSON)
{
  "name": "Tenet",
  "director": "Nolan"
}
  • @Body 데코레이터를 통해 전달할 수 있다.
@Post()
    create(@Body() movieData) { //@Body 데코레이터는 movieData안의 리퀘스트 바디를 가져오기 위함
      return movieData;
    }
  • 직접 요청하여 제공받음
@Patch('/:id') //부분 수정
    patch(@Param('id') movieId: string, @Body() updateData) { //필요한 파라미터를 요청해야함
      return {
        updateMovie:movieId, 
        ...updateData,
      };
    }
  • 쿼리 파라미터로 검색하기
@Get("search")
    search(@Query("year") searchingYear:string){
      return `We are searching for a movie made after: ${searchingYear}`
    }

2.2 Movies Service part One

  • 가짜 DB를 만들어서 테스트
  • Nest.js는 수동으로 import하지 않는다.
  • entity 만들기
  • service 생성

2.3 Movies Service part Two

  • #2.2의 데이터는 서버가 켜진 동안에만 저장됨
  • 예외처리
  • 삭제, 수정 기능 구현

2.4 DTOs and Validation part One

  • 유효성 검사
  • DTO(Data Transfer Object) 데이터 전송 객체 : 코드를 간결하게 해주고 NestJS가 들어오는 쿼리에 대해 유효성 검사를 한다.
  • TS를 이용하여 각 객체의 타입을 확인하고 유효성 검사가 가능한거다.
  • url로 보낸 값은 string이기 때문에 +를 붙여 number 타입으로 바꿔준다.
  • 하지만 entity에 number라고 타입을 명시 했기때문에 저렇게 작성하면 안된다.
//movies.service.ts
getOne(id: string): Movie {
  const movie = this.movies.find(movie => movie.id === +id); //+붙임
  if(!movie) {
    throw new NotFoundException(`Movie with ID ${id} not found.`);
  }
  return movie;
}
  • 터미널에서 값을 보고 싶다면 console.log( )로 확인하기

2.5 DTOs and Validation part Two

  • udate-dto 생성 ( update-user.dto.ts )
// ./users/dto/update-movie-dto.ts
import { IsNumber, IsString } from 'class-validator';
    export class UpdateMovieDto{
      @IsString()
      readonly title?: string;

      @IsNumber()
      readonly year?: number;

      @IsString({ each: true })
      readonly genres?: string[];
    }
// create-movie-dto.ts 파일내용 그대로 복붙 (? == 읽기전용, 부분만 수정이 가능하도록 필수가 아니게 설정)
// update-movie에서 ?를 사용하려니 service에서 맞지않는 type이라는 에러가 발생함. 아래에 다른 명령어 있느니 참고
  • UpdateMovieDto 추가
// movies.controller.ts
@Patch('/:id') //부분 수정
patch(@Param('id') movieId: number, @Body() updateData: UpdateMovieDto) { //UpdateMovieDto 추가
  return this.moviesService.update(movieId, updateData);
}
// movies.service.ts
update(id: number, updateData: UpdateMovieDto) { //UpdateMovieDto 추가
  const movie = this.getOne(id);
  this.deleteOne(id);
  this.movies.push({ ...movie, ...updateData });
}
  • update-movie.dto.ts의 내용을 간결하게 만든다.
  • PartialType 설치먼저하기 $ npm i @nestjs/mapped-types
import { PartialType } from '@nestjs/mapped-types';
import { IsNumber, IsString } from 'class-validator';
import { CreateMovieDto } from './create-movie.dto';

export class UpdateMovieDto extends PartialType(CreateMovieDto) {
    }
  • create-movie-dto.ts에서 필수 사항이 아닌 목록은 필수제외 처리해주기
//create-movie-dto.ts
import { IsNumber, IsOptional, IsString } from 'class-validator';

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

  @IsNumber()
  readonly year: number;

  @IsOptional()   //optional 추가
  @IsString({ each: true })
  readonly genres: string[];
}

2.6 Modules and Dependency Injection

  • app.module.ts 업데이트
  • app.module은 AppController와 AppService만 가지고 있어야한다.
  • 현재 MoviesContorller와 MoviesService가 app.module에 있어 이 두가지를 movies.module로 옮겨준다.
  • module이 없는경우 새로 생성하기
//터미널에 입력
$ nest
$ nest g module
//변경전
//app.modules.ts
@Module({
  imports: [],
  controllers: [MoviesController],
  providers: [MoviesService],
})
export class AppModule {}

//변경후
//app.modules.ts
import { Module } from '@nestjs/common';
import { MoviesModule } from './movies/movies.module';

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

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

@Module({
  controllers: [MoviesController],
  providers: [MoviesService],
})
export class MoviesModule {}
  • 여기서 주의할 점은 app.module은 app.controller.ts와 app.service.ts가 필요함
  • 새 폴더로 생성될 경우 app은 바깥으로 가져오면된다 (아래 경로 참조)
//폴더 경로
src
src/app.module.ts
src/app.controller.ts
src/app.service.ts
src/main.ts
>src
├─ >movies
│   ├─ >dto
│   │	├─ create-movie.dto.ts
│   │	└─ update-movie.dto.ts
│   │
│   ├─ >entities
│   │    └─ movie.entity.ts
│   │
│   ├─ movies.controller.ts
│   ├─ movies.module.ts
│   └─ movies.service.ts

//파일이 없는경우 아래 명령어로 생성하기
$ nest
$ nest g s
$ nest g co
  • NestJS는 dependency injection를 가진다.

2.7 Express on NestJS

  • Express위에서 NestJS는 돌아간다.
  • 그래서 controller에서 Request, Response 객체가 필요하면 사용할 수 있다.
@Get()
getAll(@Req() Req, @Res() Res): Movie[] { //@Req, Res를 적어주므로 Express에 접근이 가능
  return this.moviesService.getAll();
}
//그런데 NestJS는 두 개의 프레임워크를 작동하기때문에 req나 res같은 Express 객체를 직접적으로 사용하는게 좋은 방법은 아님
  • Fastify(라이브러리)로 전환가능하다.
  • Fastify는 Express보다 2배 빠르다.
  • Express와 동일하게 작동한다.
  • NestJS는 Fastify와 Express 두개의 프레임워크 위에서 동시에 작동된다.

3.0 테스트하는 부분

0개의 댓글