W14D3 Nest.js로 게시판 만들기

Jin Bae·2023년 2월 15일
0

스파르타코딩클럽

목록 보기
33/35

Creating the module, controller, service

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 모듈을 사용할 수 있다.

DTO

🔔 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.

PartialType and PickType

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) {}

ValidationPipe for validating

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.

Complete board.controller.ts code

import { 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)
  }
}

Complete board.service.ts code

The 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
    })
  }
}

Throwing an error

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"
}

Issues encountered

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

Questions

  1. Why use DTO instead of TS interface?
  2. Why use PickType/PartialType instead TS generics?
  3. Why use Lodash?

0개의 댓글