깊은바다거북
개발 공부 기록
깊은바다거북
전체 방문자
오늘
어제
  • 분류 전체보기 (219)
    • JAVA (9)
    • JavaScript (15)
    • 스파르타코딩클럽 (11)
      • [내일배움단] 웹개발 종합반 개발일지 (5)
      • [내일배움캠프] 프로젝트와 트러블 슈팅 (6)
    • SQL | NoSQL (4)
    • CS 등등 (0)
    • TIL | WIL (173)
    • 기타 에러 해결 (3)
    • 내 살 길 궁리 (4)

인기 글

최근 글

최근 댓글

태그

  • BST(이진 탐색 트리)
  • 최소 힙(Min Heap)
  • POST / GET 요청
  • 트러블 슈팅 Troubleshooting
  • 재귀 함수
  • 혼자 공부하는 자바스크립트
  • DFS(깊이우선탐색)
  • Inorder Traversal(중위 순회)
  • Leetcode
  • 점화식(Recurrence Relation)
  • 시간 복잡도
  • Backtracking(백트래킹)
  • Til
  • tree
  • 자잘한 에러 해결
  • 자바스크립트 기초 문법
  • Binary Tree(이진 트리)
  • Preorder Traversal(전위 순회)
  • TypeScript
  • Trie
  • leetcode-cli
  • 01. 미니 프로젝트
  • 자료 구조
  • 최대 힙(Max Heap)
  • 코딩테스트 연습문제
  • 팀 프로젝트
  • BFS(너비우선탐색)
  • 프로그래머스
  • TIT (Today I Troubleshot)
  • Linked List
hELLO · Designed By 정상우.
깊은바다거북

개발 공부 기록

3/3 금 (Nest.js 공식 문서를 후루룩) TIL
TIL | WIL

3/3 금 (Nest.js 공식 문서를 후루룩) TIL

2023. 3. 4. 00:43

(최종 프로젝트 진행중)

어제에 이어 git rebase 방식에 대해 알아보는 시간을 가졌다. 어제치 TIL에 업데이트하였다. 이해도가 조금 더 쌓이면 팀원들과 공유해볼 생각이다. 깃헙으로 협업할 때 더 편리하게 작업할 수 있을 것 같아서.

Nest.js의 provider, controller, module에 관해 공식 문서를 빠르게 훑으며 정리해보았다. 지난번에 게시판 CRUD를 Nest.js로 만들면서 가졌던 의문점들을 상당수 해결할 수 있었어서 좋았다. 이것 하나에 시간을 너무 지체하였나 싶기도 하지만, 들인 시간과 심리적 안정감을 저울질해보았을 때 앞으로 프로젝트를 진행하는 데 있어서 옳았던 판단이라고 생각된다.


※ 이하는 스스로 공부하며 적어둔 노트이며 불확실한 내용이 있을 수 있습니다. 학습용으로 적합하지 않음을 유념해주세요. ※

[Nest.js] 기본 공부

Nest.js 공식 문서 (영문)

Documentation | NestJS - A progressive Node.js framework
Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reactive Programming).
https://docs.nestjs.com/providers

Nest.js 공식 문서 번역 (2챕터 까지밖에 없음)

프로바이더 - 쉽게 풀어 쓴 Nest.js
초보자도 쉽게 Nest.js 를 이해할 수 있도록 풀어서 사용했습니다.
https://www.wisewiredbooks.com/nestjs/overview/04-provider.html

Nest가 사용하는 3계층 : Controller(=consumer), Service(=provider), data

@Injectable()는 의존성 주입을 위한 데코레이터.

Nest에서 발생하는 제어 역전이란, 곧 Controller에 Service를 주입하는 것이란 뜻으로 쓰인다고 봐도 될 것 같다.

Node 기반 HTTP 프레임워크는 크게 Express, Koa, Fastify가 있는데 Nest에서는 그 중 Express(platform-express)를 기본으로 사용한다. (추가 설정 필요 없음)

[Nest.js] controller

: Routing 메커니즘을 통해 어떤 컨트롤러가 어떤 요청을 받아 처리할지를 판단하게 되는 계층.

: 어떤 클래스에 @Controller를 달고 @Get 등의 데코레이터를 사용하게 되면, Nest는 그로 인해 제공된 metadata를 이용해 라우팅 지도(routing map)를 그릴 수 있게 된다.

라우팅 지도(routing map): 요청 URL과 그를 받아 처리하는 컨트롤러들을 묶은 관계도.

🚩
validation built-in CRUD 컨트롤러 빠르게 생성하기:

⇒ CLI의 nest g resource [name] 명령어를 사용한다.

Routing, 경로 나누기

API 엔드포인트

: 웹서비스에서 클라이언트가 서버의 API에 접근할 수 있는 URL

: HTTP 요청 메소드 + 라우트 경로(Route path)

// 예제3
import { Controller, Get, Param } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get('all')
  findAll(): string {
    return 'This action returns all cats';
  }

  @Get(':id')
  findOne(@Param('id') id: number): string {
    return 'This action returns one cats';
  }

  @Get('musical')
  findMusical() {
    return 'This action returns musical CATs';
  }
}

⇒ Nest는 GET /cats/musical URL 요청을 findMusical() 메서드에 매핑한다.

⇒ Nest에서 라우트 경로(Route path)는 ‘컨트롤러 경로 + 핸들러 경로’로 이루어지게 된다.

Request object, 요청 객체의 특정 속성에 직접 접근하기

Nest는 @Res()처럼 핸들러의 시그니쳐에 데코레이터를 추가하여 Nest에 주입하도록 지시하여 (Express나 Fastify) 요청 객체에 액세스할 수 있다. 세션이나 헤더 등 요청 세부 정보가 필요할 때 사용하면 된다.
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(@Req() request: Request): string {
    return 'This action returns all cats';
  }
}

(타입스크립트를 사용할 경우, 위의 예제처럼 express의 Request 타이핑을 위해서는 @types/express 패키지를 설치하면 타입 도움을 받을 수 있음 참고)

@nestjs/common 이 제공하는 Request 관련 데코레이터들:

Nest의 데코레이터Express(?)에서의 객체명
@Request(), @Req()req
@Response(), @Res()*res
@Next()next
@Session()req.session
@Param(key?: string)req.params / req.params[key]
@Body(key?: string)req.body / req.body[key]
@Query(key?: string)req.query / req.query[key]
@Headers(name?: string)req.headers / req.headers[name]
@Ip()req.ip
@HostParam()req.hosts

Resources, Nest가 제공하는 HTTP 메서드 데코레이터 목록:

@Get(), @Post(), @Put(), @Delete(), @Patch(), @Options(), and @Head().

추가로 위의 모든 메소드를 포함하는(담당하는) 엔드포인트 @All()이 있다.

Route wildcards, 경로명에 사용할 수 있는 와일드카드

?, +, *, and ()를 사용하면 RegExp에서의 의미 그대로 사용할 수 있다.

@Get('ab*cd') // 'abcd', 'ab_cd', 'abecd', 'ab-cd', 'ab.cd' 모두 가능.
findAll() {
  return 'This route uses a wildcard';
}

반대로 하이픈 ( -)과 점 (.)은 글자 그대로의 의미로 해석된다. (와일드카드 문자 X)

Status code, 상태 코드 반환

디폴트는 200, POST 요청인 경우는 201이다.

@HttpCode() 데코레이터를 핸들러 레벨에 붙이면 응답 코드를 바꿀 수 있다.

import { HttpCode } from '@nestjs/common'

@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat';
}


(여기서부터 대강 정리. 더욱 부정확할 수 있다. 다시 한 번 살펴봐야 함)


Headers, 헤더

@Header() 데코레이터를 이용한 수작업 응답 헤더 반환:

@Post()
@Header('Cache-Control', 'none')
create() {
  return 'This action adds a new cat';
}

Redirection, 경로 재할당하기

@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
  if (version && version === '5') {
    return { url: 'https://docs.nestjs.com/v5/' };
  }
}

Route parameters

@Get(':id')
findOne(@Param() params): string {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}

혹은

@Get('/articles/:id')
async getArticleById(@Param('id') articleId: number) {
  return await this.boardService.getArticleById(articleId);
}

⇒ @Params() 안에 특정 ‘값’을 넣으면 (’id’같이) 그 변수만 가리키게 되고, 아무 인자도 주지 않으면 params 전체 값들을 가지는 객체를 가리키는 매개변수가 되게 된다.

Sub-Domain Routing

@Controller 데코레이터는 들어온 HTTP 요청의 host부가 특정 값과 같은 경우에만 요청을 받도록 옵션을 줄 수 있다.

@Controller({ host: 'admin.example.com' })
export class AdminController {
  @Get()
  index(): string {
    return 'Admin page';
  }
}

이런 식으로 host부에 params를 적용시켜 따로 빼낼 수도 있음: 

@Controller({ host: ':account.example.com' })
export class AccountController {
  @Get()
  getInfo(@HostParam('account') account: string) {
    return account; // account.example.com 아마도...?
  }
}
  • 실제 반환값은 뭘까?

Scopes

모든 HTTP 요청이 개별로 처리되는 “the request/response Multi-Threaded Stateless Model”과 달리, Nest에서는 모든 요청이 ‘공유된다’… 그래서 싱글톤 인스턴스를 사용하는 게 안전함을 보장한다고.

Asynchronicity

Nest에서는 ‘유예된(deferred)’ 값을 리턴하는 async 함수도 작성 가능하다… 예를 들면 이런 것:

@Get()
async findAll(): Promise<any[]> {
  return [];
}

Request payloads, 요청 바디 다루기

@Body() 데코레이터와 손수 정의하는 DTO(Data Transfer Object) 스키마 인터페이스/클래스를 이용해 HTTP 요청의 body 내용을 읽어올 수 있다.

// create-cat.dto.tsJS

export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}
// cats.controller.tsJS

@Post()
async create(@Body() data: CreateCatDto) {
  return `This action adds a new cat, named ${data.name}`;
}

⇒ 여기서 main.ts에서의 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({ transform: true }));
  await app.listen(3000);
}
bootstrap();

ValidationPipe: create() 같은 ‘메소드 핸들러’가 받는 속성들을 검열한다. 예를 들면 CreateCatDto에서 정의된 name, age, breed속성은 ‘화이트리스트’에 속하게 되고, 이 화이트리스트에 속하지 않은 다른 모든 속성들은 반환 결과에 반영되지 않도록 한다.

받는 옵션으로 transform?: boolean, disableErrorMessages?: boolean 등이 있다…

Handling errors, 에러 핸들링

(다른 챕터에서 다룸)

Getting up and running, 컨트롤러 등록

CatsController 클래스를 만들어두었어도 그 자체로는 Nest가 얘가 존재하는지 인식할 수 없다.

controller들은 항상 @Module() 데코레이터에 속한다. 루트 모듈인 AppModule에 controller를 등록하는 예시는 다음과 같다:

// app.module.ts

import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';

@Module({
  controllers: [CatsController],
})
export class AppModule {}

⇒ AppModule이라는 자체 제작 클래스에, @Module() 데코레이터를 통해 metadata를 ‘붙여(attach)서’

  1. AppModule 클래스가 모듈 클래스로 인식되게 하였고
  1. 어떤 controller가 사용되어야 하는지 알 수 있게 되었다.

Library-specific approach

지금까지의 Nest 표준 응답 방식과 대비되는, Express나 Fastify 라이브러리 자체의 응답 객체를 사용하는 방식.

(줄임)

(참고)

Nest.js 영문 공식 문서

Documentation | NestJS - A progressive Node.js framework
Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reactive Programming).
https://docs.nestjs.com/controllers

그 한글 번역본

컨트롤러 2편 - 쉽게 풀어 쓴 Nest.js
초보자도 쉽게 Nest.js 를 이해할 수 있도록 풀어서 사용했습니다.
https://www.wisewiredbooks.com/nestjs/overview/03-controller-2.html

[Nest.js] provider

= 서비스(Application) 계층 이라고 볼 수 있다.

: 의존성으로 주입할 수 있는 클래스.

: ‘비즈니스 로직들을 presentation 계층으로 공급한다’는 의미에서 ‘공급자’

  • Service, Repository, Factory, Helper 등의 기본 클래스가 정의되어 있다.
  • 커스텀 provider를 만들 수도 있다.
  • @Injectable() 데코레이터를 달고 있다.
  • Nest의 제어 역전 컨테이너(Nest IoC container)가 관리하는 대상. 의존 관계들을 알아서 ‘해석’하고, 생명주기를 관장하는 등.

‘Service’ 클래스가 대표적인 provider

@Injectable() 데코레이터를 달고 정의된다.

The @Injectable() decorator attaches metadata, which declares that CatsService is a class that can be managed by the Nest IoC container.

의존성 주입을 할 때 생성자에 단순히 ‘타입’을 넘겨주기만 하면 알아서 새 인스턴스를 생성하고 반환해준다.

In Nest, thanks to TypeScript capabilities, it's extremely easy to manage dependencies because they are resolved just by type.
constructor(private catsService: CatsService) {}

Provider registration, 등록하기

provider(Service)와 consumer(Controller)를 다 만들었으면 app.module.ts에 controllers와 providers 항목에 등록해줌으로써 해당 ‘controller’가 이 ‘provider’ 클래스에 의존하고 있음을 Nest에게 알려줄 수 있다….

DTO와 Interface, Controller, Service가 있는 Cat 모듈의 폴더 구조는 다음과 같다:

Scopes, 생명주기

기본적으로 모든 providers의 생명은 앱과 시작과 끝을 같이한다. 즉 앱이 처음 실행될 때 모든 앱 내의 의존 관계(=딸린 providers)가 해석되어야 하고, 따라서 딸린 providers가 “생성(instantiated)”된다. 앱을 종료할 때는 모든 provider가 파괴된다.

물론 이와 다른 생명 길이를 적용해줄 수도 있다.

(참고: https://docs.nestjs.com/providers#scopes)

Custom providers, 손수 만드는 providers

기본 Providers (Services, Fatories 등) 클래스 외에도 사용자 정의 provider를 만들 수 있다. Nest의 제어 역전 컨테이너 (IoC container)가 provider 간의 의존 관계를 해석하는 게 그만큼 강력해서 가능하다.

Optional providers, 옵셔널로 사용하기

Provider를 갖다 쓰는 주체(=consumer = 서비스 객체)가 해당 provider를 제공받거나 받지 않겠다(대신 디폴트 값을 쓰겠다)고 구현할 수도 있다.

해당 provider를 주입받는 생성자 시그니처에 @Optional()를 달아주면 된다.

import { Injectable, Optional, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}

⇒ 이 ‘private httpClient: T’는 인수로 안 주어져도 괜찮다.

⇒ 이 인수 ‘private httpClient: T’는 ‘HTTP_OPTIONS’라는 또다른 provider를 주입받는다…

Property-based injection, 속성에 주입하기

‘속성 기반 주입’. 지금까지의 ‘생성자 기반 주입(constructor-based injection)’과 대비되는 개념.

…

타겟 속성에 @Inject() 데코레이터를 달아주면 된다.

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

@Injectable()
export class HttpService<T> {
  @Inject('HTTP_OPTIONS')
  private readonly httpClient: T;
}

(참고)

Nest.js 영문 공식 문서

Documentation | NestJS - A progressive Node.js framework
Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reactive Programming).
https://docs.nestjs.com/providers

그 한글 번역본

프로바이더 - 쉽게 풀어 쓴 Nest.js
초보자도 쉽게 Nest.js 를 이해할 수 있도록 풀어서 사용했습니다.
https://www.wisewiredbooks.com/nestjs/overview/04-provider.html

“Resolve” = “(변수명만으로는 알 수 없는)(문맥에 맞게) 해석한다”

용례:

  • Dependency injection 상황에서, “resolve an implementation to an interface”
  • Package manager가 일할 때, “resolve packages dependencies”
  • Web에 관련하여, “resolve a hostname”
  • Nest.js 문서에서, “When the application is bootstrapped, every dependency must be resolved, and therefore every provider has to be instantiated.”
Similarly, resolving packages dependencies usually requires installing the missing packages.

- it isn't enough to know the names of the missing components, you have to actually acquire their contents, and the contents are not predictable from the name alone.

⇒ 한 마디로 해당 패키지를 ‘설치하고 그 내용물을 풀어서 읽어들여야’ 해당 ‘이름’이 뭘 뜻하고 있는 건지 알 수 있을 때, ‘패키지 의존성을 푼다/해석한다’고 표현한다.

⇒ 한 마디로 어떤 코드(변수명)가 문맥에 의존적인 상황에 쓰임.

↔ 반대로 16진수를 10진수로 변환하거나 문자열을 전부 소문자로 변환한다거나 하는 경우엔 ‘resolve’한다고 하면 안되고 단순히 ‘transform’ 한다고 해야 한다.

이에 따라 위의 용례를 번역해보면:

  • 의존성을 주입할 때 “인터페이스가 구현되는 것을 해석한다”
  • 패키지 매니저가 일할 때 “패키지 각각에 딸린 의존 패키지들을 해석한다”
  • 웹과 관련하여 “호스트명을 (문맥에 맞게) 해석한다”
  • Nest.js 문서에서는, “애플리케이션이 실행될 때 모든 의존 관계가 (모두) 해석되어야 한다”

(참고: https://softwareengineering.stackexchange.com/questions/302404/what-does-resolving-mean-in-programing)

[Nest.js] module

@Module() 데코레이터는 Nest가 애플리케이션 구조를 만들때 사용할 수 있는 메타데이터를 제공해주는 역할

Nest에서 모듈은 기본적으로 싱글톤이다. 따라서 어떤 provider든지 여러 모듈들에서 공유할 때 ‘같은 인스턴스’임이 보장된다.

애플리케이션 그래프

: Nest가 모듈과 프로바이더 간의 관계 및 종속성을 연결하기 위해 사용하는 내부 데이터 구조

어플리케이션이 커지면 컴포넌트를 분리해야하고, 컴포넌트를 구성하는 효과적인 방법으로 여러 개의 모듈을 사용하게 된다.

모듈의 기본 구조:

@Module({
  imports: [TypeOrmModule.forFeature([Article])],
  controllers: [BoardController],
  providers: [BoardService, ArticleRepository],
})
export class BoardModule {}

⇒ 우선 @Module() 데코레이터 자체가 객체를 인자값으로 요구한다. 이 객체에 들어가는 속성은:

  • controllers: Presentation 계층(=컨트롤러 계층) 집합. 이 모듈 안에서 정의된, (애플리케이션 실행시) 인스턴스화 되어야 하는 controller의 집합
  • providers: Application 계층(=서비스 계층) 집합. Nest 인젝터(Injector: 의존성을 주입하는 Nest의 내부 모듈)가 인스턴스화시키고 적어도 이 모듈 안에선 공유될 provider의 집합
  • imports: 해당 모듈에서 필요한 provider를 제공하는/노출하는(export) 모듈 집합. providers 항목에 어떤 provider를 넣고 싶을 때… 그 provider를 실제로 제공해줄 다른 모듈.
    • 왜 최상단에 import … from으로 임포트해오지 않고 이렇게 하는 건지?
  • exports: 해당 모듈이 밖으로 제공하는 providers의 부분 집합. 이 모듈을 사용하게 될 다른 모듈이 사용할 수 있도록 노출(export)할 provider 목록.

Feature modules, 기능/특징 모듈

: CatsController 와 CatsService 같이, 같은 도메인/기능/특징을 가지는 코드들을 모아 관계를 정립시킨 모듈 (CatsModule)

// cats/cats.module.ts

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

Shared modules, 공유 모듈

Nest에서 모듈은 기본적으로 싱글톤이다. 따라서 어떤 provider든지 여러 모듈들에서 공유할 때 ‘같은 인스턴스’임이 보장된다.

모든 모듈은 자동적으로 공유 모듈이다. 한 번 생성되면 다른 모듈들에서 사용될 수 있다.

예) cat 모듈의 CatsService 인스턴스를 다른 모듈에서도 쓰고 싶다고 할 때, cat 모듈에서 CatsService를 노출해주면(=exports 항목에 추가해주면) 된다.

// cats.module.ts

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService]
})
export class CatsModule {}

이후로는 CatsModule을 임포트하는 어떤 다른 모듈이든 CatsService를 provider로 사용할 수 있고, 이 때의 CatsService는 모두 같은 인스턴스임이 보장된다.

// (내가 작성하는) some.module.ts 예시: 
import { CatsModule } from './cats.module' // ? 이렇게 모듈 자체를 임포트 해오는 게 맞나? 

@Module({
	imports: [CatsModule],
	providers: [CatsService],
})
export class SomeOtherModule {}

Module re-exporting, 모듈 재 노출

Dependency injection

Global modules, 전역 모듈

Dynamic modules, 동적 모듈



Uploaded by N2T

    'TIL | WIL' 카테고리의 다른 글
    • 3월 5일 (프로젝트 구조를 짜다) TIL
    • 3/4 토 (TypeORM 소스 파일과 공식 문서를 함께 후루룩) TIL, TIT
    • 3/2 목 (Git Rebase 조사중) TIL
    • 3/1 수 (Nest.js와 TypeORM 에러 모음) TIL
    깊은바다거북
    깊은바다거북

    티스토리툴바