NestJS로 구현하는 게시판 API 실전 가이드
반응형
목차
프로젝트 구조
src/boards/
├── constants/
│ └── board.constants.ts # 상수 정의
├── dto/
│ └── create-board.dto.ts # DTO 클래스
├── pipes/
│ └── board-status-validation.pipe.ts # 커스텀 파이프
├── board.model.ts # 도메인 모델
├── boards.controller.ts # 컨트롤러
├── boards.service.ts # 서비스
└── boards.module.ts # 모듈
도메인 모델 설계
게시글 모델 정의
// board.model.ts
export interface Board {
id: string;
title: string;
description: string;
status: BoardStatus;
}
export enum BoardStatus {
PUBLIC = 'PUBLIC',
PRIVATE = 'PRIVATE',
}
DTO 설계
// create-board.dto.ts
import { IsNotEmpty } from 'class-validator';
export class CreateBoardDto {
@IsNotEmpty()
title: string;
@IsNotEmpty()
description: string;
}
API 엔드포인트 구현
컨트롤러 구현
@Controller('boards')
export class BoardsController {
constructor(private readonly boardsService: BoardsService) {}
// 게시글 목록 조회
@Get()
getAllBoards(): Board[] {
return this.boardsService.getAllBoards();
}
// 게시글 생성
@Post()
@HttpCode(HttpStatus.CREATED)
@UsePipes(ValidationPipe)
createBoard(@Body() createBoardDto: CreateBoardDto): Board {
return this.boardsService.createBoard(createBoardDto);
}
// 게시글 상세 조회
@Get(':id')
getBoardById(
@Param('id', new ParseUUIDPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE })) id: string
): Board {
return this.boardsService.getBoardById(id);
}
// 게시글 상태 업데이트
@Patch(':id/status')
updateBoardStatus(
@Param('id', ParseUUIDPipe) id: string,
@Body('status', BoardStatusValidationPipe) status: BoardStatus,
): Board {
return this.boardsService.updateBoardStatus(id, status);
}
// 게시글 삭제
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
deleteBoard(@Param('id', ParseUUIDPipe) id: string): void {
this.boardsService.deleteBoard(id);
}
}
서비스 로직 구현
@Injectable()
export class BoardsService {
private boards: Board[] = [];
// 전체 게시글 조회
getAllBoards(): Board[] {
return [...this.boards];
}
// 게시글 생성
createBoard(createboardDto: CreateBoardDto): Board {
const board: Board = {
id: randomUUID(),
...createboardDto,
status: BoardStatus.PUBLIC,
};
this.boards.push({ ...board });
return board;
}
// 게시글 상세 조회
getBoardById(id: string): Board {
const found = this.boards.find((board) => board.id === id);
if (!found) {
throw new NotFoundException(BOARD_MESSAGES.NOT_FOUND(id));
}
return { ...found };
}
// 게시글 상태 업데이트
updateBoardStatus(id: string, status: BoardStatus): Board {
const boardIndex = this.boards.findIndex(board => board.id === id);
if (boardIndex === -1) {
throw new NotFoundException(BOARD_MESSAGES.NOT_FOUND(id));
}
this.boards[boardIndex] = {
...this.boards[boardIndex],
status
};
return { ...this.boards[boardIndex] };
}
// 게시글 삭제
deleteBoard(id: string): void {
const boardIndex = this.findBoardIndex(id);
this.boards.splice(boardIndex, 1);
}
}
유효성 검증
커스텀 파이프를 통한 상태값 검증
export class BoardStatusValidationPipe implements PipeTransform<string, BoardStatus> {
private readonly statusOptions = Object.values(BoardStatus);
transform(value: string): BoardStatus {
const upperValue = value?.toUpperCase();
if (!this.isStatusValid(upperValue)) {
throw new BadRequestException(BOARD_MESSAGES.INVALID_STATUS(value));
}
return upperValue as BoardStatus;
}
private isStatusValid(status: string): boolean {
return this.statusOptions.includes(status as BoardStatus);
}
}
에러 처리
메시지 상수화
export const BOARD_MESSAGES = {
NOT_FOUND: (id: string) => `해당 id의 게시글이 없습니다. id: ${id}`,
INVALID_STATUS: (status: string) => `올바르지 않은 게시글 상태입니다. status=${status}`,
} as const;
구현된 기능들
- 게시글 목록 조회 (
GET /boards
)- 전체 게시글 목록 반환
- 불변성을 위해 복사본 반환
- 게시글 생성 (
POST /boards
)- UUID를 통한 고유 ID 생성
- DTO를 통한 입력값 검증
- 생성 시 기본 상태는 PUBLIC
- 게시글 상세 조회 (
GET /boards/:id
)- UUID 형식 검증
- 존재하지 않는 게시글 처리
- 게시글 상태 변경 (
PATCH /boards/:id/status
)- 커스텀 파이프를 통한 상태값 검증
- 유효하지 않은 상태값 처리
- 게시글 삭제 (
DELETE /boards/:id
)- 204 No Content 응답
- 존재하지 않는 게시글 처리
핵심 구현 포인트
- 불변성 유지
- 객체의 복사본을 반환하여 의도치 않은 수정 방지
return [...this.boards]; return { ...found };
- 객체의 복사본을 반환하여 의도치 않은 수정 방지
- 유효성 검증
- DTO 데코레이터를 통한 입력값 검증
- 커스텀 파이프를 통한 상태값 검증
- UUID 형식 검증
- 에러 처리
- 메시지 상수화로 일관성 있는 에러 메시지 관리
- 적절한 HTTP 상태 코드 사용
- 코드 구조화
- 책임에 따른 명확한 계층 분리
- 재사용 가능한 컴포넌트 설계
마치며
이 예제는 NestJS의 핵심 기능들을 활용하여 실제 프로덕션에서 사용할 수 있는 게시판 API를 구현하는 방법을 보여줍니다. 특히 다음과 같은 NestJS의 장점들을 잘 활용했습니다:
- 데코레이터를 통한 선언적 프로그래밍
- 파이프를 통한 검증 로직 분리
- 의존성 주입을 통한 느슨한 결합
- TypeScript의 타입 시스템 활용
개선 가능한 부분
- 데이터베이스 연동 (TypeORM/Prisma)
- 인증/인가 구현
- 페이지네이션 구현
- 검색 기능 추가
- 테스트 코드 보강
참고자료
반응형
'JavaScript > NestJs' 카테고리의 다른 글
NestJS CLI 인식 문제 해결하기: PATH 설정 가이드 (1) | 2024.11.10 |
---|---|
NestJS 시작하기: 실전 입문 가이드 (2) | 2024.11.09 |
댓글