NestJS (1) - 시작하기

707·2022년 8월 17일
0

NestJS

목록 보기
1/1
post-thumbnail

인프런 NestJS 강의 공부 내용 (링크)

nest 시작하기

nest new [프로젝트명]
nest new ./ (현재 위치한 디렉토리에 설치)

nestjs 기본 파일 구조

  1. .eslintrc.js : 문법오류체크, 코드포매터
  2. prettierrc : 코드포매터
  3. nest-cli.json : nest 프로젝트 자체 설정이 필요한 경우 : src root 등
  4. package.json : script, dependecies version
  5. src 폴더 : 프로젝트 메인 로직
  • main.ts : 어플리케이션 생성. 엔트리포인트. App.module(루트모듈)

nest 로직 흐름

어플리케이션 실행

npm run start:dev

main.ts : 포트번호 지정(기본 3000)

client "http://localhost:3000/" get요청
👉

  • express : server.js -> route -> controller (endpoint)
  • nest : main.ts -> App.module -> App.controller -> App.service (endpoint)

NestJS 모듈이란?

AppModule (root module)
ㄴ user Module
ㄴ board Module
...

모듈은 @Module() 데코레이터로 주석이 달린 클래스.
@Module() 데코레이터는 Nest가 애플레케이션 구조를 구성하는데 사용하는 메타데이터를 제공함.
각 응용프로그램에는 하나 이상의 모듈(루트모듈)이 있다. 루트 모듈은 Nest가 사용하는 시작점이다.
즉, nest에는 반드시 app.module이 하나는 있어야한다.

모듈은 밀접하게 관련된 기능 집합으로 구성요소를 구성하는 효과적인 방법이다.
즉, 모듈은 기능별로 만든다!

같은 기능에 해당하는 것들은 하나의 모듈 폴더 안에 넣어서 사용한다.
User관련 기능을 담당하는 User Module 안에 user controller, user service, user entity... 이렇게 담아둠.

모듈은 기본적으로 싱글톤이다. 여러 모듈 간에 쉽게 공급자의 동일한 인스턴스를 공유할 수 있다.
즉,
어떤 작은 기능을 담당하는 모듈을 하나 만들어두면 이 하나를 user모듈, board모듈, chat모듈 등등에 가지고 와서 사용할 수 있고
각각의 모듈은 하나의 인스턴스를 바라보고 있음. (매번 새로 생성되는 것이 아님.)

Module 생성하기

user모듈과 nft모듈을 생성할거임
기존에 있던 src폴더 내에 main.ts와 app.module.ts 제외하고 삭제. test 폴더도 삭제.

nest g module market

g : generate
module : module만들꺼임
user : 생성할 모듈의 이름.

cli이용하면 자동으로 파일 생성되고, app.module.ts 파일에 가지고와짐.

Controller란?

들어오는 요청을 처리하고 클라이언트에 응답을 반환함.
컨트롤러는 @Controller 데코레이터로 클래스를 데코레이션하여 정의됨.

클래스의 앞에 클래스의 정의를 데코레이션을 이용하여 설정해주는 것.

  • 핸들러란?
    @Get, @Post, @Delete 등과 같은 데코레이터로 장식된 컨트롤러 클래스 내의 단순한 메소드
    클래스 내부에서 http메소드를 데코레이터를 이용해서 설정해주고 해당 요청이 있을 때 핸들러 실행.

Controller 생성하기

nest g controller market --no-spec

기본적으로 -no-spec 안붙이면 test파일이 함께 생성됨.
지금은 하나하나씩 만들어볼거기때문에 이거 빼고 컨트롤러파일만 생성

자동으로 컨트롤러 파일 생성 후 모듈파일에 가지고와짐.

cli의 실행순서

  1. src내에서 market 폴더 찾음
  2. market 폴더 내에 controller 파일 생성
  3. market 폴더 내에 module 파일 찾기
  4. module 파일에 controller 넣어줌

이제 컨트롤러 클래스 내부에 직접 핸들러를 하나씩 만들면 됨.

Provers, Service란?

Provider란?

프로바이더는 nest의 기본개념이다. 대부분의 기본 nest 클래스는 서비스, 리포지토리, 팩토리, 헬퍼 등 프로바이더로 취급될 수 있다.
프로바이더의 주요 아이디어는 종속성으로 주입 할 수 있다는 것이다.
즉, 객체는 서로 다양한 관계를 만들 수 있으며, 객체의 인스턴스를 연결하는 기능은 대부분 nest 런타임 시스템에 위임될 수 있다.

🤯 이게 먼말이고...

컨트롤러1에 서비스1, 서비스2, 리포지토리1, 팩토리1 ... 여러가지가 필요함.
이 각가의 객체들을 컨트롤러에 넣어줄거임. == 종속성을 주입한다!
이 서비스1, 서비스2, 리포지토리1 등등이 프로바이더

Service란?

서비스는 소프트웨어 개발 내의 공통 개념이며 nestJS, javascript에서만 쓰이는 개념이 아니다.
@Injectable 데코레이터로 감싸져서 모듈에 제공되며, 이 서비스 인스턴스는 애플리케이션 전체에서 사용될 수 있음.
서비스는 컨트롤러에서 데이터의 유효성을 체크하거나, db에 아이템 생성 등의 작업을 하는 부분을 처리함 (서비스로직)
== express에서 controller에 서비스로직을 담았던 것처럼 그걸 nest에서는 service 계층에서 함.

서비스 클래스는 @Injectable() 데코레이터를 앞에 붙여서 감싸줘야함.

서비스를 컨트롤러에서 사용하는 법

바로 사용가능한 것은 아님.
종속성 주입을 해줘야함. Dependency Injection

constructor 생성자함수에 서비스를 가져와서 인스턴스 생성하는 방식.

서비스와 같은 이런 프로바이더를 사용하기 위해서는 이걸 nest에 등록을 먼저 해줘야한다. 단순히 import해서 사용할 파일에 가지고오면 되는 것이 아님.
등록은 module 파일에서 할 수 있다.
module파일의 providers 항목 안에 해당 모듈에서 사용하고자 하는 Provider를 넣어주면 됨.

예전 프로젝트에서 repository를 사용하려고 그냥 service에 import해서 가져왔을 때는 안됐었는데 module의 provider에 추가해주니까 에러가 안났었음..
이런 이유에서였나보다.
그때는 진짜 왜 이걸 이런식으로 써야하는지도 모르고 코드 따라해보기 급급했는데 이제 조금 감이 잡히는 느낌?
근데 이렇게 단순히 import하는 방식이 아니라 종속성주입으로 provider에 추가해서 nest에게 알려주는 방식을 사용하는 이유는 뭘까?? 싱글톤인거랑 관련이 있나???
아무튼 강의 더 봐보겠음.

Service 만들기

service 내부에서는 주로 db관련 로직을 처리함.

nest g service market --no-spec

cli를 이용해서 service를 생성하면
생성된 파일 내에 Injectable() 데코레이터가 있고 nestJS는 이걸 이용해서 다른 컴포넌트에서든, nest의 어디에서든 이 서비스를 사용할 수 있게 만들어준다.
module에도 자동으로 Providers내에 service 추가됨.

service -> controller에서 사용할 수 있게

종속성 주입해주기
service import한 다음 constructor에 추가해주기

import { MarketController } from './market.service';

@Controller('market')
export class MarketController {
  marketService: MarketService;

  constructor(marketService: MarketService) {
    this.marketService = marketService;
  }
}

👇 간단하게 (타입스크립트의 private 접근제한자 기능 활용)

import { MarketController } from './market.service';

@Controller('market')
export class MarketController {
  constructor(private marketService: MarketService) {}
}

자바스크립트에는 private, protected 같은게 없음..
솔리디티랑 타입스크립트랑 자바스크립트랑 막 쓰다보니까 뭐에 뭐가 있고 없는지가 헷갈린다ㅠㅠ

✏️ 접근제한자 (public, protected, private)을 생성자(constructor) 파라미터에 선언하면 접근제한자가 사용된 생성자 파라미터 (marketService)는 암묵적으로 클래스 프로퍼티로 선언된다.
그래서 marketService: MarketService;와 this.marketService... 이 코드가 불필요함. 🔥

컨스트럭터로 가지고 왔으면 이제 클래스 내부에선 this.marketService.메소드명() 이런 식으로 사용 가능.

CRUD 구현하기

market model 정의하기

게시물에 필요한 데이터 market.model.ts

  1. 인터페이스 : 변수의 타입만 체크
  2. 클래스 : 변수의 타입 체크 + 인스턴스 생성
    두가지 중 한가지 방법으로 모델 생성

우선 구조만 정의하기 위해 인터페이스만 이용해서 만들어볼거임.

DTO

Data Transfer Object
계층간 데이터 교환을 위한 객체
DB에서 데이터를 가지고와 Service나 Controller 등으로 보낼 때 사용하는 객체
데이터가 네트워크를 통해 전송되는 방법을 정의하는 객체
인터페이스, 클래스를 이용해 정의 가능하지만 공식문서에서는 클래스를 이용하는 것을 추천함!

DTO를 왜 쓸까?

데이터 유효성을 체크하는데 효율적
더 안정적인 코드로 만들어 줌. 타입스크립트의 타입 역할

클라이언트 -> req -> controller -> service -> DB

req.body의 내용을 controller에서 한번, service에 한번 전달해야하는데
이때마다 각각의 명칭과 타입을 체크해줌
body객체의 property가 변할때마다 이런걸 일일이 수정해줘야하는데
만약 엄청 많은 프로퍼티가 필요하고 수정이 빈번한 경우 어플리케이션의 유지보수가 힘들어짐
이런 이유로 DTO를 사용함.

  • dto 사용
    market 디렉토리 내에 dto 폴더 생성
    class로 dto 정의
    controller와 Service에서 인자 하나하나를 전달하는 대신 dto를 인자로 전달

Pipe

pipe는 Injectable() 데코레이터가 달린 클래스.
data transformation과 data validation을 위해 사용됨.
Nest는 메소드가 호출되기 직전에 파이프를 삽입하고, 파이프는 메소드로 향하는 인수를 수신하고 이에 대해 작동함.
음.. 약간 리덕스사가 쓸 때 같이 요청을 중간에 가지고와서 우선적으로 처리되는 역할인듯?

클라이언트 -> request -> server router handler
이런 순서로 흘러갔다면
클라이언트 -> request -> pipe(req.body transformation, validation 처리) -> handler / error
중간에 pipe의 검증단계에서 요청의 인수가 설정과 맞지 않다면 handler함수 실행하지 않고 error 발생

Data Transformation

들어오는 데이터를 원하는 타입으로 변경해주는 것
string '7' -> number '7'

Data Validation

들어오는 데이터가 지정한 형식과 일치하는지 확인
input 데이터의 길이, 형식 등등을 검증함

Pipe 사용하기 : Binding Pipes

파이프를 사용하는 방법은 크게 세가지로 나눠짐

  1. Handler-level Pipes
  • controller 파일에서 아래와 같이 핸들러에 붙여서 사용.
  • 모든 파라미터에 적용됨
@Post
@UsePipes(pipe)
createBoard (
  @Body('title') title,
  @Body('content') content
) {}
  • usePipes(), ValidationPipe는 nestjs에서 제공해줌
  1. Parameter-level Pipes
  • 핸들러레벨보다 좁은 개념.
  • 파라미터 하나에만 적용되는 파이프 (아래의 코드에서는 title 파라미터에만 적용됨.)
 @Post
 createBoard (
   @Body('title', ParameterPipe) title,
   @Body('content') content
 ) {}
  1. Global-level Pipes
  • 애플리케이션 레벨의 파이프. 클라이언트에게서 들어오는 모든 요청에 적용됨
  • 가장 상단 파일인 main.ts 에서 설정
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(GlobalPipes);
  await app.listen(3000);
}

NestJS에서 기본적으로 제공해주는 파이프가 있음

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • DefaultValuePipe

예를들어 /:id 처럼 params로 받아오는 값에서 숫자만 받고 싶은데 abc같은 문자열을 보낸 경우
id 파라미터에 ParseIntPipe를 넣어두면 핸들러 실행하지 않고 바로 에러를 response로 보내줌

Pipe로 유효성 체크하기

create api에서 빈 값이 넘어오거나 하는 경우를 체크해줌.

npm i class-validator class-transformer --save

IsEmpty, IsArray, IsBoolean 등등의 데코레이션을 제공해줌
=> 이 데코레이션을 기존에 만들어두었던 dto파일의 인자값들에 작성해주고, controller의 mintToken 핸들러에도 핸들러레벨로 파이프를 넣어줌
그러면 자동으로 DTO에 작성해둔 파이프 검증을 해줌

빈 값으로 요청을 보내면 에러가 response로 옴

특정 게시물을 찾을 때 없는 경우

Service 계층에 만들어둔 메소드 내에서 NotFoundException 사용

  getTokenById(tid: number): Token {
    const found = this.tokens.find((token) => token.tid === tid);

    // 찾는 게시물이 없는 경우의 예외처리
    if (!found) {
      throw new NotFoundException(`Can't find item with id ${tid}`);
    }

    return found;
  }

리턴:

{
  "statusCode": 404,
  "error": "Not Found"
}

커스텀 파이프를 이용한 유효성체크

지금까지는 nest에서 제공해주는 built-in pipe를 사용했음.
원하는 형태로 커스텀해서 파이프를 사용할 수도 있음.
class로 pipe를 만들되, PipeTransform 인터페이스를 상속받아서 만들어줌.
PipeTransform은 모든 파이프에서 구현해줘야하는 인터페이스임!
얘를 상속 받고 pipe 클래스 내부에서 transform() 메소드를 필수적으로 구현해줘야함. 이 메소드는 Nest에서 인자값들을 처리할 때 사용됨.

transform() 메소드

인자값으로 2가지를 받음

  1. value : 처리가 된 인자의 값
  2. metadata : 인자에 대한 메타데이터를 포함한 객체

이 메소드에서 return된 값이 route 핸들러로 전달됨.
만약 exception이 발생한 경우 그대로 클라이언트로 에러전달.

=> 이거는 많이 써보면서 익히기

TypeORM

ORM

Object Relational Mapping
객체 관계형 매핑
객체와 관계형 데이터베이스의 데이터를 자동으로 변형 및 연결하는 작업
객체 - 데이터베이스 변형에 유연하게 대응 가능

  • 쿼리
SELECT \* FROM boards WHERE title='Hello' AND status='PUBLIC'
  • typeORM
const boards = Board.find({title:'Hello', status: 'PUBLIC'})

TypeORM의 특징과 이점

  • 모델을 기반으로 데이터베이스 테이블 체계를 자동으로 생성
  • 데이터베이스에서 개체를 쉽게 삽입, 업데이트 및 삭제 가능
  • 테이블 간의 매핑 (일대일, 일대다, 다대다) 만들어줌
  • 간단한 CLI 명령 제공
  • 다른 모듈과 쉽게 통합됨

필요한 모듈

  • @nestjs/typeorm : 네스트에서 typeORM 사용 연동해줌
  • typeorm : typeORM 모듈
  • mysql2 : mysql db 모듈

참조 : https://docs.nestjs.com/techniques/database


(typeORM은 다른 게시물에서 따로 더 정리할 예정)

0개의 댓글