소스 코드

[리포지 링크]

들어가기 전에

이번 장에서는 지난 시간에 만든 2단계 의존성 주입에 이어, ‘3단계 몽고DB 연동’을 다뤄보도록 하겠다.

1. mongoose 스키마 작성하기

순서 1. 스키마 작성을 위해, root 경로에 mongoose 의존성 패키지를 설치한다

npm install @nestjs/mongoose mongoose

순서 2. src 밑에 blog.schema.ts 파일을 생성하고, 도큐먼트 타입과 스키마를 작성한다

// src > blog.schema.ts

import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Document } from "mongoose";

// 1. 블로그이면서 도큐먼트인 타입 정의
export type BlogDocument = Blog & Document;

@Schema() // 2. 스키마를 나타냄
export class Blog {
    @Prop() // 3. 스키마의 프로퍼티임을 나타냄
    id: string;

    @Prop()
    title: string;

    @Prop()
    content: string;

    @Prop()
    name: string;

    @Prop()
    createdDt: Date;

    @Prop()
    updatedDt: Date;
}

// 4. 스키마 생성
export const BlogSchema = SchemaFactory.createForClass(Blog);

1번. 몽고DB의 도큐먼트로 사용할 수 있는 BlogDocument 타입을 생성한다. type <이름> 오른쪽에 두 개의 타입을 &로 연결해 교차타입을 만든다. 교차타입이란 예를 들어 C = A & B로 만들었다면 C는 A와 B의 모든 프로퍼티를 가지고 있어야 한다. 반면 C = A | B 형식은 A 또는 B의 프로퍼티를 가지고 있어야 한다는 의미다.

2번. mongoose를 사용하기 위해, 스키마를 통해 만들어질 모델을 정의한다. @Schema는 그런 모델을 쉽게 만들 수 있도록 데코레이터를 제공한다.

3번. @Prop은 모델의 프로퍼티다. @Prop({ required: true }) 등으로 옵션을 추가할 수 있다.

4번. SchemaFactory.createForClass() 함수를 사용해 스키마를 생성한다. 내부적으로는 mongoose의 new Schema를 사용한다.

2. 몽고DB를 사용하는 리포지토리 추가하기

리포지토리(blog.repository.ts)에는 이미 파일을 사용하는 리포지토리 클래스가 있다. 여기에 몽고DB를 위한 BlogMongoRepository를 하나 더 추가한다.

// blog.repository.ts

// 몽고DB 연동을 위해 임포트
import { InjectModel } from "@nestjs/mongoose";
import { Model } from "mongoose";
import { Blog, BlogDocument } from './blog.schema';
// 

export interface BlogRepository {
// ... 생략 ...   
}

@Injectable()
export class BlogFileRepository implements BlogRepository {
// ... 생략 ...
}

// ★ 추가
@Injectable()
// 1. 몽고디비용 리포지토리
export class BlogMongoRepository implements BlogRepository {
    // 2. Model<BlogDocument> 타입인 blogModel 주입
    constructor(@InjectModel(Blog.name) private blogModel: Model<BlogDocument>) { }

    // 3. 모든 게시글을 읽어오는 함수
    async getAllPost(): Promise<PostDto[]> {
        return await this.blogModel.find().exec();
    }

    // 4. 게시글 작성
    async createPost(posDto: PostDto) {
        const createPost = {
            ...posDto,
            createdDt: new Date(),
            updatedDt: new Date(),
        };
        this.blogModel.create(createPost);
    }

    // 5. 하나의 게시글 읽기
    async getPost(id: String): Promise<PostDto> {
        return await this.blogModel.findById(id);
    }

    // 6. 하나의 게시글 삭제
    async deletePost(id: string) {
        await this.blogModel.findByIdAndDelete(id);
    }

    // 7. 게시글 업데이트
    async updatePost(id: String, postDto: PostDto) {
        const updatePost = {
            id,
            ...postDto,
            updatePost: new Date()
        };
        await this.blogModel.findByIdAndUpdate(id, updatePost);
    }
}

1번. BlogRepository 인터페이스를 구현한 BlogMongoRepository이다. 같은 인터페이스를 구현했기 때문에 이전에 만든 BlogFileRepository와 같은 메서드를 가지고 있다. 서비스에서 인터페이스 타입을 사용한다면 큰 변경 없이 사용할 수 있는 장점이 있다.

2번. 게시글을 읽기, 쓰기, 수정, 삭제 메서드를 가지고 있는 모델을 주입받는다. @nestjs/mongoose의 @InjectModel 데코레이터를 사용하면 된다.

3번. getAllPost()는 모든 게시글을 읽어오는 함수다. blogModel의 find() 함수를 사용한다.

4번. createPost() 메서드는 게시글을 작성하는 메서드다. postDto에서 title, content, name 정보를 가져오고 생성일(catedDt), 수정일(updatedDt) 정보는 createPost 객체를 만들 때 현재 시간을 사용한다.

5번. getPost() 메서드는 id를 매개변수로 받으며 하나의 게시글 정보를 가져올때 사용한다. blogModel의 findById() 함수를 사용한다.

6번. deletePost() 메서드는 id를 매개변수로 받아서 게시글을 삭제한다. blogModel의 findByIdAndDelet() 메서드를 사용한다.

7번. updatePost() 메서드는 게시글 정보를 업데이트할 때 사용한다. updatedDt 정보만 현재 시간으로 변경해 업데이트한다. 업데이트 시에는 blogModel의 findByIdAndUpdate() 함수를 사용한다.

3. 서비스 코드 변경

서비스 코드는 리포지토리 의존성만 변경한다. BlogFileRepository를 사용하는 부분을 이전에 만든 BlogMongoRepository로 변경해준다. 인터페이스를 사용했기에 얻은 이득이다. 상세 클래스를 인터페이스에 맞춰서 만들면 인터페이스를 사용하는 코드들은 변경하지 않아도 된다.

// blog.service.ts

// 1. 파일리포지터리를 쓰지 않기 때문에 생략
// import { BlogFileRepository, BlogRepository } from './blog.repository';
import { BlogMongoRepository } from './blog.repository';

@Injectable()
export class BlogService {
    // 2. 생성자를 통한 의존성 주입
    constructor(private blogRepository: BlogMongoRepository) { }
    
// 나머지 코드 생략

4. 모듈에 몽고 디비 설정과 프로바이더 설정 추가하기

import { Module } from "@nestjs/common";
// 필요한 모듈 추가
import { MongooseModule } from "@nestjs/mongoose";
import { Blog, BlogSchema } from './blog.schema';
// 
import { BlogController } from "./blog.controller";
import { BlogService } from './blog.service';
import { BlogFileRepository, BlogMongoRepository } from "./blog.repository";

@Module({
  imports: [
    // 1. 몽고DB 연결 설정
    MongooseModule.forRoot(
      'mongodb+srv://<유저명>:<비번>!@cluster0.vbpuhau.mongodb.net/blog'
    ),
    // 2. 몽고DB 스키마 설정
    MongooseModule.forFeature([{ name: Blog.name, schema: BlogSchema }])
  ],
  controllers: [BlogController],
  // 3. 프로바이더 설정
  providers: [BlogService, BlogFileRepository, BlogMongoRepository]
})
export class AppModule { }

1번. MongooseModule.forRoot 를 사용해 몽고 디비 연결을 설정한다. 마지막에 blog 부분에 주목하자

2번. MongooseModule.forFeature로 스키마를 설정한다. name 항목에 이름을 shcema에는 만들어둔 BlogSchema를 넣어준다.

3번. BlogMongoRepository를 프로바이더 배열에 추가한다.

5. 테스트 결과

몽고DB에 요청한 작업이 잘 작동함을 볼 수 있다.

image-20230824114327641

마무리하며…

  • 내용이 많고 실무에 적용하려면, 더한 연습이 필요할 것 같다. 다음 포스트에서 실무에서 개발했던 프론트웹에 필요한 API를 만들어 보겠다.

참고

다음에 다루게 될 것

  • NestJS로 직접 기획한 API 만들기

댓글남기기