JavaScript

수입/지출관리 어플리케이션 백엔드 REST API 구축 기록 [NestJS/MongoDB]

rexondex 2024. 12. 27. 01:20

npm run start -> successfully started
'localhost:3000'에서 문구 출력 -> 어플리케이션 실행 성공
MongoDB 실행중
수입/지출관리 기능을 처리할 'income-expense' 파일들


NestJS를 사용해 수입/지출 관리를 위한 기본 REST API를 구축하고 실행한 것입니다.
주요 작업 내용과 코드를 기록한 문서입니다.

 


1. NestJS 프로젝트 생성

  • NestJS는 백엔드 애플리케이션 프레임워크로, 모듈 기반 구조와 데코레이터를 활용한 선언적 코딩 스타일을 제공합니다.
  • 초기 프로젝트 설정과 IncomeExpenseModule 생성은 애플리케이션의 구조를 정리하는 작업입니다.

2. Mongoose 및 MongoDB 연결

  • Mongoose를 통해 MongoDB와 통신할 수 있도록 설정했습니다.
  • MongoDB는 NoSQL 데이터베이스로, 수입/지출 데이터를 저장할 데이터베이스로 사용되었습니다.
  • MongooseModule.forFeature를 사용해 IncomeExpense라는 Mongoose 모델을 등록했습니다.
    • 이 모델은 MongoDB 컬렉션과 매핑되며, 데이터를 다룰 때 스키마를 기반으로 유효성을 검증합니다.

3. REST API 구성

  • NestJS에서 컨트롤러서비스 계층을 구현했습니다:
    1. Controller (컨트롤러):
      • API의 엔드포인트를 정의하고, 클라이언트의 요청을 처리합니다.
      • 예: POST /income-expenses로 수입/지출 데이터를 생성.
    2. Service (서비스 계층):
      • 비즈니스 로직을 처리합니다.
      • 컨트롤러에서 받은 요청 데이터를 Mongoose 모델을 통해 MongoDB에 저장하거나, 조회/수정/삭제합니다.
    3. DTO (Data Transfer Object):
      • 클라이언트가 전달하는 데이터를 구조화하고, 유효성 검사를 쉽게 하기 위해 정의된 객체입니다.

4. REST API CRUD 작업

다음 CRUD 기능을 API로 구현했습니다:

  1. Create:
    • 수입/지출 데이터를 생성.
    • 예: POST /income-expenses로 데이터 전송 → MongoDB에 저장.
  2. Read (All):
    • 모든 수입/지출 데이터를 조회.
    • 예: GET /income-expenses.
  3. Read (One):
    • 특정 수입/지출 데이터를 조회.
    • 예: GET /income-expenses/:id.
  4. Update:
    • 기존 데이터를 수정.
    • 예: PUT /income-expenses/:id로 데이터 업데이트.
  5. Delete:
    • 데이터를 삭제.
    • 예: DELETE /income-expenses/:id.

5. 에러 해결

구현 중 다음과 같은 문제를 해결했습니다:

  • 타입 선언 문제: IncomeExpense 타입이 값으로 사용될 때 발생한 오류.
    • 해결: Mongoose 모델 이름을 문자열('IncomeExpense')로 명시.
  • NestJS 종속성 문제: IncomeExpenseService가 IncomeExpenseModel을 주입받지 못할 때 발생한 오류.
    • 해결: MongooseModule.forFeature를 통해 올바르게 모델 등록.

6. Hello, World 실행 성공

  • 프로젝트 실행 후, REST API 엔드포인트에 정상적으로 접근하여 데이터를 생성/조회할 수 있었습니다.
  • 이는 다음을 의미합니다:
    1. NestJS 애플리케이션이 성공적으로 실행됨.
    2. MongoDB와 연결된 Mongoose 모델이 정상적으로 작동.
    3. REST API가 클라이언트 요청을 처리할 준비가 완료됨.

■ 작업의 목적과 결과

  • 목적: 개인 재정 관리를 위한 백엔드 시스템 구축.
    • MongoDB에 데이터를 저장하고, 필요한 데이터를 클라이언트에 제공하는 RESTful API.
  • 결과: NestJS 기반의 API 서버가 성공적으로 실행되었으며, 데이터베이스와 상호작용 가능한 CRUD 기능이 구현됨.

다음 단계

  1. API 테스트:
    • Postman 또는 cURL을 사용해 모든 엔드포인트를 테스트합니다.
    • 예: 수입/지출 데이터 생성, 수정, 삭제 요청을 보내고 데이터베이스에서 결과 확인.
  2. 프론트엔드 연결:
    • Next.js와 같은 프레임워크를 사용해 사용자 인터페이스를 개발.
    • 이 API를 통해 데이터를 시각화하고 사용자와 상호작용할 수 있도록 설계.
  3. 기능 확장:
    • 인증/인가 추가 (예: JWT).
    • 더 복잡한 데이터 분석 기능 (예: 월별 통계).
    • 데이터 유효성 검사 강화.

지금까지의 작업은 백엔드 애플리케이션의 기본 뼈대를 완성한 상태이며, 다음 단계에서는 이를 기반으로 추가 기능과 UI를 통합해 나가면 됩니다.


NestJS 프로젝트를 시작하고 실행하기 위해 콘솔에서 사용한 명령어들을 정리

 

1. NestJS 프로젝트 생성

# 새로운 NestJS 프로젝트 생성
npx @nestjs/cli new personal-finance-backend
  • personal-finance-backend: 프로젝트 이름.
  • 생성 과정에서 패키지 매니저를 선택 (예: npm 또는 yarn).

2. 프로젝트 폴더로 이동

cd personal-finance-backend

3. Mongoose 및 MongoDB 설정

# Mongoose와 관련 패키지 설치
npm install @nestjs/mongoose mongoose
  • @nestjs/mongoose: NestJS에서 Mongoose를 쉽게 사용할 수 있도록 지원.
  • mongoose: MongoDB와 통신하는 ODM(Object Document Mapper) 라이브러리.

4. IncomeExpense 모듈 및 리소스 생성

NestJS CLI로 모듈과 기본 파일 생성:

# 수입/지출 모듈 생성
npx nest generate module income-expense
# 수입/지출 컨트롤러 및 서비스 생성
npx nest generate controller income-expense npx nest generate service income-expense
  • income-expense.module.ts, income-expense.controller.ts, income-expense.service.ts 파일이 자동으로 생성됨.

5. 애플리케이션 실행

NestJS 서버 실행:

npm run start
  • 서버는 기본적으로 http://localhost:3000에서 실행됩니다.
  • 실행 중 코드 변경 시 자동으로 서버가 재시작되도록 npm run start:dev를 사용할 수도 있습니다.

6. MongoDB 연결 테스트

  • .env 파일이나 설정 파일에 MongoDB 연결 URI 추가:
DATABASE_URI=mongodb://localhost:27017/finance
  • app.module.ts에서 MongooseModule로 연결 설정:
# typescript
imports: [ MongooseModule.forRoot(process.env.DATABASE_URI), ],

7. 수동으로 DTO 파일 생성 (선택 사항)

# src/income-expense/dto 폴더 생성
mkdir src/income-expense/dto

# DTO 파일 생성 (예: CreateIncomeExpenseDto 및 UpdateIncomeExpenseDto)

8. Docker로 MongoDB 실행 (로컬 환경에서 필요한 경우에 / 선택 사항)

# Docker로 MongoDB 실행
docker run --name mongodb -d -p 27017:27017 mongo
  • Docker를 통해 MongoDB 컨테이너를 실행합니다.
  • MongoDB가 이미 설치되어 있다면 로컬 MongoDB 서비스만 시작하면 됩니다.

실행 결과

 

이 작업을 통해 다음 상태에 도달했습니다:

  1. NestJS 애플리케이션이 MongoDB와 연결됨.
  2. IncomeExpenseModule을 통해 기본적인 REST API CRUD 기능이 작동.

서버가 npm run start로 실행 가능.


구현에 사용한 income-expense 파일 내용

 

// src/income-expense/models/income-expense.controller.ts
import { Controller, Post, Get, Body, Param, Put, Delete } from '@nestjs/common';
import { IncomeExpenseService } from './income-expense.service';
import { CreateIncomeExpenseDto, UpdateIncomeExpenseDto } from './dto/income-expense.dto';



@Controller('income-expenses')
export class IncomeExpenseController {
    constructor(private readonly incomeExpenseService: IncomeExpenseService) { }

    @Post()
    create(@Body() createIncomeExpenseDto: CreateIncomeExpenseDto) {
        return this.incomeExpenseService.create(createIncomeExpenseDto);
    }

    @Get()
    findAll() {
        return this.incomeExpenseService.findAll();
    }

    @Get(':id')
    findOne(@Param('id') id: string) {
        return this.incomeExpenseService.findOne(id);
    }

    @Put(':id')
    update(@Param('id') id: string, @Body() updateIncomeExpenseDto: UpdateIncomeExpenseDto) {
        return this.incomeExpenseService.update(id, updateIncomeExpenseDto);
    }

    @Delete(':id')
    delete(@Param('id') id: string) {
        return this.incomeExpenseService.delete(id);
    }
}
// src/income-expense/models/income-expense.model.ts
import { Schema, Document } from 'mongoose';

export const IncomeExpenseSchema = new Schema({
    type: { type: String, required: true },
    amount: { type: Number, required: true },
    description: { type: String, required: false },
});

// Document를 확장한 인터페이스 정의
export interface IncomeExpense extends Document {
    type: string;
    amount: number;
    description?: string;
}
// src/income-expense/income-expense.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { IncomeExpenseService } from './income-expense.service';
import { IncomeExpenseController } from './income-expense.controller';
import { IncomeExpenseSchema } from './income-expense.model';

@Module({
    imports: [
        MongooseModule.forFeature([{ name: 'IncomeExpense', schema: IncomeExpenseSchema }]),
    ],
    controllers: [IncomeExpenseController],
    providers: [IncomeExpenseService],
    exports: [IncomeExpenseService], // 필요 시 다른 모듈에서 사용 가능하도록 export
})
export class IncomeExpenseModule { }
// src/income-expense/income-expense.schema.ts
import { Schema, Document } from 'mongoose';

export const IncomeExpenseSchema = new Schema({
    type: { type: String, enum: ['income', 'expense'], required: true },
    amount: { type: Number, required: true },
    description: { type: String },
    date: { type: Date, default: Date.now },
});

export interface IncomeExpense extends Document {
    type: string;
    amount: number;
    description?: string;
    date: Date;
}
// src/income-expense/income-expense.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { IncomeExpense } from './income-expense.model'; // 모델 타입 임포트
import { CreateIncomeExpenseDto } from './dto/income-expense.dto'; // DTO 임포트

@Injectable()
export class IncomeExpenseService {
    constructor(
        @InjectModel('IncomeExpense') private readonly incomeExpenseModel: Model<IncomeExpense>, // 모델 이름을 문자열로 사용
    ) { }

    async create(createIncomeExpenseDto: CreateIncomeExpenseDto): Promise<IncomeExpense> {
        const createdIncomeExpense = new this.incomeExpenseModel(createIncomeExpenseDto);
        return createdIncomeExpense.save();
    }

    async findAll(): Promise<IncomeExpense[]> {
        return this.incomeExpenseModel.find().exec();
    }

    async findOne(id: string): Promise<IncomeExpense> {
        return this.incomeExpenseModel.findById(id).exec();
    }

    async update(id: string, updateIncomeExpenseDto: any): Promise<IncomeExpense> {
        return this.incomeExpenseModel.findByIdAndUpdate(id, updateIncomeExpenseDto, { new: true }).exec();
    }

    async delete(id: string): Promise<void> {
        await this.incomeExpenseModel.findByIdAndDelete(id).exec();
    }
}
// src/income-expense/dto/income-expense.dto.ts
export class CreateIncomeExpenseDto {
    type: string;
    amount: number;
    description?: string;
}

export class UpdateIncomeExpenseDto {
    amount: number;
    description?: string;
}

 

'JavaScript' 카테고리의 다른 글

Node.js로 경량 API서버 구현해보기 [Express.js]  (0) 2024.10.28