Node.js server-side application
TypeScript로 만들어졌고 TypeScript를 완전히 지원한다!
- 독립적인 프레임워크는 아니고 Express/Fastify와 같은 HTTP 서버 프레임워크 위에 얹어져서 일관된 API로 노출시켜주는 Adaptor 느낌쓰
- 기존에 사용했던 Express의 경우 프로젝트 구조가 자유로운 프레임워크였기 때문에 구조화에 있어서 개인적으로 고민이 많았는데 Nest에서 이 부분을 보완해주는 것 같다.
npm i -g @nestjs/cli
nest new project-name
--strict
flag를 사용해서 프로젝트 생성nest new project-name --strict
src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.ts
eslint
)와 formatter(prettier
)가 미리 설치되어 있다. (formatters vs linters)# Lint and autofix with eslint
$ npm run lint
# Format with prettier
$ npm run format
prettier
를 저장할 때마다, 또는 붙여넣기 할 때마다 적용시키고 싶어서 나는 다음과 같이 설정해주었다.Crtl
+ ,
> format 검색 (User, Workspace 모두 동일하게 세팅).vscode/settings.json
에 다음 내용 추가(저장해도 제대로 적용이 안되는 경우에만) "[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
.prettierrc
에서 원하는대로 설정 {
"singleQuote": true,
"trailingComma": "all",
"tabWidth": 2,
"printWidth": 80,
"endOfLine": "auto"
}
.eslintrc.js
의 rules field에 다음 내용 추가rules: {
…,
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
},
들어오는 요청을 처리하고 클라이언트에 응답을 반환하는 역할
💡 validation 기능이 내장된 CRUD controller를 CLI의 CRUD generator를 사용해 신속하게 생성 가능:
nest g resource [name]
@Get()
, @Post()
, @Put()
, @Delete()
, @Patch()
, @Options()
, @Head()
) 지원string
, number
, boolean
)을 return하면 serialize하지 않고 값을 그냥 보냄@HttpCode(...)
를 사용해 변경 가능@Res()
decorator를 사용하면 response.status(200).send()
와 같이 이용 가능다만 Nest는
@Res()
나@Next()
를 handler에서 감지하면 Nest의 Standard approach는 비활성화된다.따라서 특정 기능만 response object를 주입해 사용하고 나머지는 Nest framwork의 기능을 사용하도록 두고 싶다면 반드시
@Res({ passthrough: true })
로 option을 설정해주어야 한다.
의존성 주입이 가능한 services, repositories, factories, helpers 등의 providers로 취급되는 basic Nest classes
@Injectable()
이라는 decorator를 사용하여 다른 객체에 주입 가능하도록 설정DEFAULT
: singleton providers의 lifecycle이 전체 application의 lifecycle과 동기화
REQUEST
: provider의 새로운 instance가 각각의 incoming request에 대해 생성되고 요청 처리가 완료된 후에는 instance가 garbage-collected됨
import { Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {}
TRANSIENT
: transient providers는 consumers 사이에 공유되지 않고, provider를 주입하는 각각의 consumer는 새로운 전용 instance를 받음
import { Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.TRANSIENT })
export class CacheManager {}
// provider registration
{
provide: 'CACHE_MANAGER',
useClass: CacheManager,
scope: Scope.TRANSIENT,
}
@Module()
decorator로 annotate한 class
@Module()
decorator는 다음 property들을 가지는 단일 객체를 인자로 받는다.
providers | Nest injector에 의해 인스턴스화되고 적어도 이 module 전체에서 공유될 수 있는 providers |
controllers | 인스턴스화되어야 하는 이 module에 정의된 controllers |
imports | 이 module에 필요한 providers를 export하는 module들을 import하는 목록 |
exports | 이 module에서 제공되는 providers의 하위 집합으로 다른 module에서 이 module을 import해서 사용 가능 (provider 자체나 해당하는 token(provide value)을 사용할 수 있음) |
Nest에서 module은 default로 singleton ⇒ 같은 provider의 instance를 여러 module들에서 쉽게 공유 가능!!
ex. CatsService
라는 provider의 instance를 다른 module들에서 공유하기
CatsService
provider를 module의 exports array에 추가한다.
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을 import하는 어떤 module에서든 CatsService에 접근 가능하며 같은 instance를 공유한다.
@Global()
decorator를 사용하면 module이 global-scope가 되며 해당 module을 import array에 추가하지 않아도 export한 provider를 사용할 수 있다.
import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Global()
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
input data의 transformation이나 validation을 위해 사용 (변환 가능하면 변환 후 controller route handler로 넘어가고 변환이 실패하면 exception 발생)
ValidationPipe
ParseIntPipe
ParseFloatPipe
ParseBoolPipe
ParseArrayPipe
ParseUUIDPipe
ParseEnumPipe
DefaultValuePipe
ParseFilePipe
Parse*
pipe@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}
+) option을 사용할 수도 있음@Get(':id')
async findOne(
@Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }))
id: number,
) {
return this.catsService.findOne(id);
}
GET localhost:3000/abc
{
"statusCode": 400,
"message": "Validation failed (numeric string is expected)",
"error": "Bad Request"
}
class-validator
를 사용하면 DTO를 정의한 후 ValidationPipe
를 binding해서 손쉽게 validation이 가능하다.create-cat.dto.ts
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
@IsString()
name: string;
@IsInt()
age: number;
@IsString()
breed: string;
}
cats.controller.ts
@Post()
async create(
@Body(new ValidationPipe()) createCatDto: CreateCatDto,
) {
this.catsService.create(createCatDto);
}
main.ts
에 app.useGlobalPipes()
를 이용해 global scoped pipe로 설정할 수 있다. 모든 route handler에 적용된다.async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();