14 June, 2022

NestJS에서 MongoDB 사용하기 (with Mongoose)

NestJS에서 MongoDB 사용을 위한 기본 설정
Photo by Kevin Ku on Unsplash.
tweet
share

Please support me with,

Table Of Contents
# # # # #

최근에 구름IDE에서 사용할 백엔드를 개발하는데 NestJSMongoDB를 이용해보았다. 평소 같으면 PostgreSQL로 데이터베이스를 구축했을텐데 구름IDE 기본 제공 데이터베이스에 MySQL과 MongoDB 뿐이었고, 조만간 MongoDB를 이용한 개발 일을 하게될 수도 있어서 공부도 할 겸 MongoDB로 데이터베이스를 구축했다.

NestJS에서 MongoDB를 사용하는 방법은 공식 문서에 잘 나와있지만 한 번 더 정리해보자.

NestJS Mongoose 설치

아래 명령어로 @nestjs/mongoosemongoose 패키지를 설치하자.

npm i @nestjs/mongoose mongoose

MongoDB 데이터베이스 연결

설치하고 나면 데이터베이스를 사용할 NestJS 모듈에 MongooseModule을 임포트 해준다.

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [MongooseModule.forRoot('mongodb://localhost/nest')],
})
export class AppModule {}

이렇게 하면 mongodb://localhost/nest 라는 데이터베이스에 대한 Connection을 생성한다. 생성된 Connection은 DI를 이용해 서비스나 컨트롤러에 주입할 수 있다.

import { Injectable } from '@nestjs/common';
import { InjectConnection } from '@nestjs/mongoose';
import { Connection } from 'mongoose';

@Injectable()
export class DatabaseService {
  constructor(@InjectConnection() private connection: Connection) {}
}

하지만 직접 사용해 본 결과, Connection을 사용할 일은 transaction을 할 때 말곤 딱히 없었다.

MongooseModule.forRoot() 메서드를 이용해 MongoDB에 연결하기 위해 아래와 같은 방법들도 가능하다.

MongooseModule.forRoot('mongodb://{username}:{password}@localhost/nest?options...');
MongooseModule.forRoot('mongodb://localhost/nest', {
  user: '{username}',
  pass: '{password}',
  // and other options...
});

Mongoose Schema 정의

다음은 데이터베이스 Model을 정의하는 방법이다. 아래는 공식 문서에 나온 Model 정의 방법이다.

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

export type CatDocument = Cat & Document;

@Schema()
export class Cat {
  @Prop()
  name: string;

  @Prop()
  age: number;

  @Prop()
  breed: string;
}

export const CatSchema = SchemaFactory.createForClass(Cat);

@Schema() 데코레이터로 Schema를 정의하고, @Prop() 데코레이터로 Model의 각 필드를 정의한다. MongoDB에서는 테이블을 Collection, 테이블 안에 있는 데이터를 Document라고 부르는데 Schema는 Collection과 정의된 Model을 이어주는 역할을 한다.

그런데 이 @nestjs/mongoose 패키지는 다른 ORM 패키지들과 다른 불편함을 가지고 있다. 바로 해당 Model의 Document에 대한 타입과 Schema를 별도로 정의해줘야 한다는 점이다. TypeORM과 비교를 하자면 Entity만 정의하면 간단하게 모든게 설정되는 것과는 달리 추가적인 노력이 들어가줘야 되는 것이다.

이 불편함 때문에 TypeORM을 써보려고도 했는데, TypeORM + MongoDB는 공식 문서도 없고 찾아보니 TypeORM이 MongoDB 버전 4부터는 지원하지 않는다는 글이 있어서 그냥 @nestjs/mongoose 패키지를 사용했다.

어쨌든 이렇게 정의한 Model은 MongooseModule.forFeature() 메서드를 이용해 특정 모듈에서 사용하도록 만들 수 있다.

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { Cat, CatSchema } from './schemas/cat.schema';

@Module({
  imports: [MongooseModule.forFeature([{ name: Cat.name, schema: CatSchema }])],
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

그럼 이제 데이터베이스에 연결할 경우 cat 이라는 이름의 Collection이 없으면 자동으로 생성된다.

생성된 Model은 DI를 이용해 서비스나 컨트롤러에 주입할 수 있다.

import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cat, CatDocument } from './schemas/cat.schema';

@Injectable()
export class CatsService {
  constructor(@InjectModel(Cat.name) private catModel: Model<CatDocument>) {}
}

여러 개의 데이터베이스 연결

@nestjs/mongoose 는 여러 데이터베이스에 대한 Connection을 생성할 수 있는데, 그 방법은 간단하다. MongooseModule.forRoot()를 여러 개 사용하면 된다.

단, 이 때 옵션에 connectionName을 설정해줘야 한다.

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [
    MongooseModule.forRoot('mongodb://localhost/test', {
      connectionName: 'cats',
    }),
    MongooseModule.forRoot('mongodb://localhost/users', {
      connectionName: 'users',
    }),
  ],
})
export class AppModule {}

이렇게만 하면 기존의 CatsService에 있던 catModel에서 에러가 난다. 데이터베이스 Connection이 여러 개인데 어디에서 catModel을 가져와야 할 지 모르기 때문이다.

따라서 CatsModuleCatsService를 아래와 같이 바꿔준다.

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { Cat, CatSchema } from './schemas/cat.schema';

@Module({
  imports: [MongooseModule.forFeature([{ name: Cat.name, schema: CatSchema }], 'cats')],
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}
import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cat, CatDocument } from './schemas/cat.schema';

@Injectable()
export class CatsService {
  constructor(@InjectModel(Cat.name, 'cats') private catModel: Model<CatDocument>) {}
}

만약 Connection을 DI를 통해 사용한다면 Connection에도 connectionName을 지정해줘야 한다.

import { Injectable } from '@nestjs/common';
import { InjectConnection } from '@nestjs/mongoose';
import { Connection } from 'mongoose';

@Injectable()
export class CatsService {
  constructor(@InjectConnection('cats') private connection: Connection) {}
}

마무리

오늘 포스팅은 여기서 끝! 앞으로 한동안은 NestJS + MongoDB에 관한 글을 올릴 예정이다.

공식 문서가 모든 기능을 다 담아서 정리해둔 게 아니라 처음 개발을 진행할 때 알 수 없는 에러에도 많이 봉착하고 여간 헤맨 게 아니었는데 다른 사람들은 이 글을 보고 적어도 덜 헤맸으면 좋겠다.

Tags
Copyright 2022, tk2rush90, All rights reserved