코딩캠프/내일배움캠프

[ TIL ] 02.15(수) 66일차

고랑E 2023. 2. 15. 21:00
728x90

Nest.js 입문

 

게시판 만들어보기 Part.1

 

board 모듈, 컨트롤러,서비스 생성

// 모듈
nest g mo board

// 컨트롤러
nest g co board

// 서비스
nest g s board

 

import { Controller } from '@nestjs/common';

@Controller('board')
export class BoardController {}

컨트롤러 데코레이터 옆에 문자열은 라우팅이다.!!!

이 컨트롤러는 /board 라는 주소 아래로 요청을 하면 해당 컨트롤러에서 처리

 

 

게시판 만들어보기 Part.2

 

lodash - Javascript로 코딩할 때 유용하게 사용할 수 있는 유틸성 패키지

 

lodash 패키지 설치

npm i lodash

 

ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져올 수 있게

tsconfig.json 수정

"esModuleInterop": true

 

board.controller.ts

import { Controller, Delete, Get, Post, Put } from '@nestjs/common';
import { BoardService } from './board.service';

@Controller('board')
export class BoardController {
  // 서비스 주입
  constructor(private readonly boardService: BoardService) {}

  //전체 게시글 가져오기
  @Get('/articles')
  getArticles() {
    return this.boardService.getArticles();
  }

  //게시글 상세보기
  @Get('/articles/:id')
  getArticleById() {
    return this.boardService.getArticleById(id);
  }

  // 게시글 작성
  @Post('/articles')
  createArticle() {
    return this.boardService.createArticle();
  }

  // 게시글 수정
  @Put('/articles/:id')
  updateArticle() {
    return this.boardService.updateArticle(id);
  }

  // 게시글 삭제
  @Delete('/articles/:id')
  deleteArticle() {
    return this.boardService.deleteArticle(id);
  }
}

POST, PUT의 경우에는 클라이언트로부터 데이터를 전달받아야 생성하거나 수정합니다.

Express에서는 req.body에 담긴 데이터를 받았는데 Nest.js에서는 어떻게 받아올까??

 

Nest.js에서는 클라이언트로부터 데이터를 받거나 데이터를 줘야 할 때는 DTO를 사용해야 합니다.

 

DTO (Data Transfer Object)는 클라이언트와 서버 간의 데이터 전송에 사용되는 객체

DTO는 서버로 전송되는 데이터를 표현하는 객체로,

데이터를 송신하는 클라이언트와 수신하는 서버 사이에서 인터페이스의 역할

 

 

class-validator 패키지로 입력값 유효성 검사

npm i class-validator

 

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

 

update-article.dto.ts

import { IsNumber, IsString } from 'class-validator';

export class UpdateArticleDto {
  @IsString()
  readonly title: string;

  @IsString()
  readonly content: string;

  @IsNumber()
  readonly password: number;
}

 

delete-article.dto.ts

import { IsNumber } from 'class-validator';

export class DeleteArticleDto {
  @IsNumber()
  readonly password: number;
}

 

@IsString(), @IsNumber() 는 class-validator에서 제공하는 데코레이터이다.

 

각 dto.ts 코드를 보면 비슷하다

@nestjs/mapped-types 패키지를 사용해서 PartialType, PickType 등을 사용하면 코드를 깔끔하게 줄일수있다.

 

@nestjs/mapped-types 패키지 설치

npm i @nestjs/mapped-types

 

설치 중 오류가 난다면(의존성 이슈)

해당하는 패키지를 삭제 하고 제일 나중에 설치 하자

npm uninstall class-validator
npm i @nestjs/mapped-types
npm i class-validator

 

 

@nestjs/mapped-types 패키지를 사용해서 아래와 같이 코드를 줄일수있다.

 

update-article.dto.ts

import { PartialType } from '@nestjs/mapped-types';
import { CreateArticleDto } from './create-article.dto';

export class UpdateArticleDto extends PartialType(CreateArticleDto) {}

 

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

 

 

DTO의 유효성 검사를 하기 위해서 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());
  await app.listen(3000);
}
bootstrap();

 

유효성 검사 데코레이터는 DTO에서 한 번만 정의하면 된다.

 

board.controller.ts

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')
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

import {
  Injectable,
  NotFoundException,
  UnauthorizedException,
} from '@nestjs/common';
import _ from 'lodash';

@Injectable()
export class BoardService {
  // 데이터베이스를 사용하지 않아 일단은 배열로 구현
  private articles = [];

  // 게시글 비밀번호를 저장하기 위한 Map 객체입니다.
  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) {
    // id 먼저 할당
    // 1번부터 시작 => 현재 배열의 크기 + 1
    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(
        `Article 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(
        `Article password is not correct. id: ${id}`,
      );
    }

    this.articles = this.articles.filter((article) => {
      return article.id !== id;
    });
  }
}