본문 바로가기
✨BACKEND/📍NestJS

NestJS 게시판 CRUD 구현하기

by 짱돌보리 2025. 7. 27.
728x90

NestJS 게시판 CRUD 구현하기

본 포스팅은 인프런 강의에서 배운 내용을 개인적으로 정리한 기록입니다.

지난 글에서는 NestJS의 기본 개념에 대해 정리해봤다. 그 내용을 바탕으로 실제 게시판 CRUD 기능을 구현해보자!

[✨BACKEND/📍NestJS] - NestJS 개념

 

NestJS 개념

🐱NestJS 개념본 포스팅은 인프런 강의에서 배운 내용을 개인적으로 정리한 기록입니다.📍 NestJS란https://docs.nestjs.com/ Documentation | NestJS - A progressive Node.js frameworkNest is a framework for building efficient, s

bori-note.tistory.com

 

📍 게시판 만들기

Board Module 만들기

nest g module boards

 

Board Controller 만들기

nest g controller boards --no-spec

// --no-spec: 테스트 코드 생성 X

 

Board Service 만들기

nest g service boards --no-spec

 

Board Service를 Board Controller에서 이용할 수 있게 해주기

  • Nest JS에서 Dependency Injection은 클래스의 Constructor안에서 이루어진다.
  • 자바스크립트에서는 private같은 접근 제한자(Access modifier)를 사용할수 없지만 Typescript에서는 사용 가능.
// 전
class Example {
  private service: SomeService;

  constructor(service: SomeService) {
    this.service = service;
  }

  doSomething() {
    this.service.작동();
  }
}
// 후
class Example {
  constructor(private service: SomeService) {}

  doSomething() {
    this.service.작동();
  }
}

 

위 코드에서는 private 키워드 하나로,

  1. service: SomeService 프로퍼티 생성
  2. this.service = service 할당까지 자동으로 처리

Board Model 만들기

  • Model은 보통 "데이터의 구조"를 정의하는 역할을 한다.
  • "Board가 어떤 속성을 가지는가?", "DB 테이블에 어떻게 저장되는가?"

모델을 정의하기 위해서는

Interface - 변수의 타입만을 체크

classes - 변수의 타입도 체크하고 인스턴스 또한 생성할 수 있음

export interface Board {
  id: string
  title: string
  description: string
  status: BoardStatus
}

export enum BoardStatus {
  PUBLIC = 'PUBLIC',
  PRIVATE = 'PRIVATE',
}
유형 설명 사용 상황
1. Interface 또는 Class 데이터의 타입을 정의 (예: id, title 등) DB 없이 메모리에서 데이터 관리할 때
2. Entity ORM에서 DB 테이블과 매핑되는 클래스 TypeORM, Sequelize 등 RDB 사용 시
3. Schema NoSQL(MongoDB) 문서 구조 정의 Mongoose(MongoDB) 사용 시

 

Board DTO 만들기

// board.service.ts
import { v1 as uuid } from 'uuid'

createBoard(title: string, description: string) {
    const board: Board = {
      id: uuid(),
      title: title,
      description: description,
      status: BoardStatus.PUBLIC
    }

    this.boards.push(board)
    return board
  }

 

위 부분을 아래와 같이 클래스로 정의해 쓸 수 있다!

export class CreateBoardDto {
  title: string
  description: string
}
@Post()
  createBoard(@Body() createBoardDto: CreateBoardDto): Board {
    return this.boardsService.createBoard(createBoardDto)
  }
createBoard(createBoardDto: CreateBoardDto) {
    const { title, description } = createBoardDto

    const board: Board = {
      id: uuid(),
      title,
      description,
      status: BoardStatus.PUBLIC
    }

    this.boards.push(board)
    return board
  }

📍CRUD 구현하기

✨READ

  • 사용자가 GET /boards 요청
  • BoardsController.getAllBoard() 실행
  • this.boardsService.getAllBoards() 실행
  • → 빈 배열 [] 반환
@Controller('boards')
export class BoardsController {
  constructor(private boardsService: BoardsService) {}

  // 모든 게시물 가져오기
  @Get('/')
  getAllBoard(): Board[] {
    return this.boardsService.getAllBoards()
  }
}
@Injectable()
export class BoardsService {
  private boards: Board[] = []

  getAllBoards(): Board[] {
    return this.boards
  }
}

 

ID로 특정 게시물 가져오기

getBoardById(id: string): Board {
    return this.boards.find(board => board.id === id)
  }
@Get('/:id')
  getBoardById(@Param('id') id: string) {
    return this.boardsService.getBoardById(id)
  }

✨CREATE

게시글 id는?

  • DB 없이 로컬 메모리로 게시글을 저장하려면, 각 게시글(Board)에 고유한 id를 부여해야 한다.
  • uuid를 사용하면 간편하게 유니크한 문자열 ID를 생성할 수 있다.
yarn add -D @types/uuid
// board.service.ts
import { v1 as uuid } from 'uuid'

createBoard(title: string, description: string) {
    const board: Board = {
      id: uuid(),
      title: title,
      description: description,
      status: BoardStatus.PUBLIC
    }

    this.boards.push(board)
    return board
  }
버전 방식 특징
v1 시간 기반 현재 시간 + 시스템 정보 기반으로 고유 ID 생성
v4 랜덤 기반 랜덤 값으로 생성 (가장 일반적)
// boards.controller.ts
 @Post()
  createBoard(
    @Body('title') title: string,
    @Body('description') description: string
  ): Board {
    return this.boardsService.createBoard(title, description)
  }


@Body 로 HTTP 요청의 본문(body)에 담긴 데이터를 추출하자!

postman으로 post 확인해보자!

✨UPDATE

특정 게시물의 상태 업데이트

updateBoardStatus(id: string, status: BoardStatus): Board {
    const board = this.getBoardById(id)
    board.status = status
    return board
  }
@Patch('/:id/status')
  updateBoard(@Param('id') id: string, @Body('status') status: BoardStatus) {
    return this.boardsService.updateBoardStatus(id, status)
  }

✨DELETE

ID로 특정 게시물 지우기

  deleteBoard(id: string): void {
    this.boards = this.boards.filter(board => board.id !== id)
  }
  @Delete('/:id')
  deleteBoard(@Param('id') id: string) {
    return this.boardsService.deleteBoard(id)
  }

deleteremove 차이

용어 뜻 / 역할 예시
delete 바로 DB에서 데이터를 바로 삭제 boardRepository.delete(id) → id로 딱 삭제
remove 먼저 데이터 찾아서(객체로 가져와서) 그걸 삭제 boardRepository.findOne()로 먼저 찾고, 찾은 객체를 remove()로 삭제

비유하자면…

  • delete
    주소만 알려주면, 우체국에서 바로 편지(데이터) 버리는 느낌
  • remove
    우체국에 가서 편지를 직접 꺼내서 버리는 느낌 
  • 그래서 편지를 먼저 찾아야 하고, 그 편지가 있는지 꼭 확인함
  • 데이터 ID만 알면 바로 삭제하고 싶으면 → delete()
  • 객체(데이터)를 가지고 있고, 그 상태로 삭제하고 싶으면 → remove()

📍PIPE로 게시물 생성시 유효성 체크하기

GitHub - typestack/class-validator: Decorator-based property validation for classes.

 

GitHub - typestack/class-validator: Decorator-based property validation for classes.

Decorator-based property validation for classes. Contribute to typestack/class-validator development by creating an account on GitHub.

github.com

 

yarn add class-validator class-transformer --save
export class CreateBoardDto {
  @IsNotEmpty()
  title: string

  @IsNotEmpty()
  description: string
}

특정 게시물을 찾을 때 없는 경우 결과 값 처리

특정 게시물을 id로 가져올 때 없는 게시물 id면 결과값으로 아무값이 안 온다.

→ 에러 표시를 해주자! : 예외 인스턴스 사용하기

getBoardById(id: string): Board {
    const found = this.boards.find(board => board.id === id)
    if (!found) {
      throw new NotFoundException(`Can't find Board with id ${id}`)
    }
    return found
  }

없는 게시물을 지우려 할 때 결과 값 처리

deleteBoard(id: string): void {
    const found = this.getBoardById(id) // 여기서 예외처리해주니까 따로 해줄필요X
    this.boards = this.boards.filter(board => board.id !== found.id)
  }

커스텀 파이프를 이용한 유효성 체크

  1. PipeTransform: 인터페이스 구현
  2. transform() 메서드 안에서 데이터 처리
  3. 라우트 핸들러에 적용 (@Body, @Param, 등)

 

 

이번 글에서는 NestJS를 활용해 게시판 CRUD 기능을 구현해보았다. 처음 접해봐서 넘 어려웠지만.. 기본적인 서비스, 컨트롤러, 레포지토리 구조를 직접 만들어보며 NestJS의 흐름을 익힐 수 있었다.

 

다음 포스팅에서는 인증 기능을 추가해, 로그인 및 회원가입 기능을 공부해보겠다!!! 🥳🥳

'✨BACKEND > 📍NestJS' 카테고리의 다른 글

NestJS 회원가입/로그인 구현하기(feat. MySQL)  (1) 2025.08.30
NestJS 인증 기능 구현하기  (8) 2025.08.10
NestJS 개념  (6) 2025.07.20