board
라는 module, controller, service를 만들어야한다.
Terminal에서 src
폴더로 가서 다음과 같은 명령을 쓰면 board
폴더가 만들어지고 안에 파일이 만들어진다.
nest g mo board
nest g co board
nest g s board
Package를 설치한 때는 root 폴더에서 설치하면 된다.
Lodash package를 설치하는 방법:
npm i lodash
tsconfig.json
에 "esModuleInterop": true
를 추가한다. ES6 모듈 사양을 준수하면서 CommonJS 모듈을 사용할 수 있다.
🔔 In Nest.js, DTO (Data Transfer Object) is necessary to receive or send data from the client.
Use class-validator
and class-transformer
packages.
class-validator
provides various functions to help validate the input data.
create-article.dto.ts
import { IsNumber, IsString } from 'class-validator';
export class CreateArticleDto {
@IsString()
readonly title: string;
@IsString()
readonly content: string;
@IsNumber()
readonly password: number;
}
@IsString()
and @IsNumber()
are decorators provided by class-validator
. If the title or content is not in string, it will throw a error 400.
update-article.dto.ts
will require the same DTO as create-article.dto.ts
. You can copy and paste the code, but it will be cleaner to inherit PartialType
from @nestjd/mapped-types
.
Using PartialType
makes the UpdateArticleDto
class a subset of CreateArticleDto
.
For the DeleteArticleDto
class, it only needs to validate the password field. It doesn't necessarily need the title and content when using PartialType
. Instead, PickType
can be used from @nestjs/mapped-types
to pick a field from the parent class.
Install @nestjs/mapped-types
:
npm i @nestjs/mapped-types
The installation may fail due to the dependency of class-validator
. If it fails, try:
npm uninstall class-validator
npm i @nestjs/mapped-types
npm i class-validator
Using PartialType
in update-article.dto.ts
:
import { PartialType } from '@nestjs/mapped-types'
import { CreateArticleDto } from './create-article.dto'
export class UpdateArticleDto extends PartialType(CreateArticleDto) {}
Using PickType
in delete-article-dto.ts
:
import { PickType } from '@nestjs/mapped-types';
import { CreateArticleDto } from './create-article.dto';
export class DeleteArticleDto extends PickType(CreateArticleDto, [
'password',
] as const) {}
To use ValidationPipe
, add the following lines in main.ts
.
import { ValidationPipe } from '@nestjs/common'; // Add this line
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 })); // Add this line
await app.listen(3000);
}
bootstrap();
The option transform: true
allows to change the parameter received as a string into a number type.
board.controller.ts
codeimport { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'
import { BoardService } from './board.service'
import { CreateArticleDto } from './create-article.dto'
import { DeleteArticleDto } from './delete-article.dto'
import { UpdateArticleDto } from './update-article.dto'
@Controller('board') // routing path is /board -> localhost:3000/board
export class BoardController {
// 서비스 주입
constructor(private readonly boardService: BoardService) {}
// 게시물 목록 가져오기
@Get('/articles')
getArticles() {
return this.boardService.getArticles()
}
// 게시물 상세보기
@Get('/articles/:id')
getArticleById(@Param('id') articleId: number) {
return this.boardService.getArticleById(articleId)
}
// 게시물 작성
@Post('/articles')
createArticle(@Body() data: CreateArticleDto) {
return this.boardService.createArticle(
data.title,
data.content,
data.password,
)
}
// 게시물 수정
@Put('/articles/:id')
updateArticle(
@Param('id') articleId: number,
@Body() data: UpdateArticleDto,
) {
return this.boardService.updateArticle(
articleId,
data.title,
data.content,
data.password,
)
}
// 게시물 삭제
@Delete('/articles/:id')
deleteArticle(
@Param('id') articleId: number,
@Body() data: DeleteArticleDto,
) {
return this.boardService.deleteArticle(articleId, data.password)
}
}
board.service.ts
codeThe following code does not use a repository yet. It will store the board in memory.
import {
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common'
import _ from 'lodash'
@Injectable()
export class BoardService {
private articles = []
private articlePasswords = new Map()
// 게시물 목록 가져오기
getArticles() {
return this.articles
}
// 게시물 상세보기
getArticleById(id: number) {
return this.articles.find((article) => {
return article.id === id
})
}
// 게시물 작성
createArticle(title: string, content: string, password: number) {
const articleId = this.articles.length + 1
this.articles.push({ id: articleId, title, content })
this.articlePasswords.set(articleId, password)
return articleId
}
// 게시물 수정
updateArticle(id: number, title: string, content: string, password: number) {
if (this.articlePasswords.get(id) != password) {
throw new UnauthorizedException(`Password is not correct. id: ${id}`)
}
const article = this.getArticleById(id)
if (_.isNil(article)) {
throw new NotFoundException(`Article not found. id: ${id}`)
}
article.title = title
article.content = content
}
// 게시물 삭제
deleteArticle(id: number, password: number) {
if (this.articlePasswords.get(id) != password) {
throw new UnauthorizedException(`Password is not correct. id: ${id}`)
}
this.articles = this.articles.filter((article) => {
return article.id !== id
})
}
}
See more built-in HTTP exceptions in NestJS
Some exceptions are built in by Nest.js and are used in board.service.ts
, including UnauthorizedException
and NotFoundException
. This will return the correct HTTP status according to the error and the message inside is returned as a message property.
Sample response for wrong password when deleting an article. The HTTP status is 401 Unauthorized:
{
"statusCode": 401,
"message": "Password is not correct. id: 1",
"error": "Unauthorized"
}
Prettier:
The following settings were used in .prettierrc
to prevent clashing (with ESLint? not sure)
{
"singleQuote": true,
"trailingComma": "all",
"tabWidth": 2,
"semi": false,
"endOfLine": "lf"
}
ESLint