2020.12.12 - [Language/Javascript] - [Node.js] MongoDB Mac에서 brew로 다운받고 Node.js로 연결하기
MongoDB 다운로드 방법은 위 포스트에서 확인하길 바란다.
이 글은 NestJS에서 어떤식으로 Mongo DB를 사용하는지 정리한 글이다. NestJS에서 mongoose를 활용한 세팅과 Schema 선언에 대한 기본적인 내용을 다루고 있다. NestJS에 대한 기본적인 내용을 알고 있다고 가정한다.
1. NestJS - MongoDB 연결
먼저 mongodb 전용 orm 패키지인 mongoose를 받아주자!
npm install --save @nestjs/mongoose mongoose
다운로드 받았다면 먼저 앱이 시작할 때 mongodb와 connection을 설정 해주어야 한다. 이 부분은 앱이 시작할 때 데이터 베이스와 커넥션을 맺어주는 부분이다.
// src/app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
// process.env.MONGO_URL 부분은 하드 코딩해도 괜찮다. "mongodb://localhost:27017"
imports: [MongooseModule.forRoot(process.env.MONGO_URL)],
})
export class AppModule {}
//여러 데이터 베이스를 사용하면 아래처럼 사용할 수 있다.
// 이 경우에는 forFeature에서 connectionName을 명시해줘야한다.
// src/app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
// process.env.MONGO_URL 부분은 하드 코딩해도 괜찮다. "mongodb://localhost:27017"
imports: [
MongooseModule.forRoot(process.env.MONGO_URL, {connectionName: 'default'}),
MongooseModule.forRoot(process.env.MONGO_URL, {connectionName: 'replica'}),
],
})
export class AppModule {}
만약 @nest/config를 이용해 환경변수에서 값을 가져 온다면 forRootAsync를 이용해 아래처럼 작성할 수 있다.
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
uri: configService.get<string>('MONGODB_URL'),
}),
inject: [ConfigService],
});
],
})
export class AppModule {}
forRoot와 forRootAsync의 차이점은 해당 모듈을 생성을 동기적으로 수행할지 아니면 비동기적으로 수행할지 결정하는 차이이다. 만약 데이터 베이스 연결을 위한 설정을 다른 파일로부터 읽어와서 Mongoose모듈을 생성한다면 비동기적으로 수행하는 것이기 때문에 forRootAsync를 사용하면 되고 아니면 즉 하드 코딩 되어 있다면 forRoot를 사용하면 된다.
추가로 커넥션과 관련된 부분도 커스텀화 해서 사용할 수 있다. 이 부분은 mongoose 쿼리를 오버라이드하는 패키지에서 사용하면 된다.
가령 mongoose-cache 패키지들의 대부분은 mongoose.Query의 exec 함수를 오버라이드 해서 사용하는데 아래와 connection.base 사용할 mongoose 패키지가 들어 있다.
useFactory: (config: ConfigService) => ({
uri: config.get("MONGO_URL"),
user: config.get("MONGO_USER"),
password: config.get("MONGO_PASSWORD"),
connectionFactory: (connection) => {
// connection.base => mongoose 와 동일
return connection;
},
}),
2. 모델 정의
스키마는 모델에 대한 정의를 의미한다. 우리가 코드로 스키마를 작성하면 Mongoose에서 자동으로 몽고 DB에 우리가 설정한 대로 데이터베이스를 구성해준다. 스키마는 모듈안에 작성하면 되고 아래와 같은 디렉토리에서 진행한다고 생각하면 된다.
이 부분은 직접 생성해도 되고 아래 처럼 nest generate 명령어를 통해 생성해도 된다.
nest g mo user
nest g s user
디렉토리는 아래와 같다.
src
|- models
|- user
|- schemas
|- user.schema.ts
|- user.module.ts
|- user.service.ts
2.1 스키마 생성
이제 스키마를 정의 해보자.
// /src/user/schema/user.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
// MongoDB의 가장 작은 단위가 Document, 모듈에서 사용할 타입을 export 시켜줌
export type UserDocument = User & Document;
// 아래와 같이 timestamp 설정도 가능하다.
// createdAt과 updatedAt둘 중에 하나만 사용하고 싶다면 아래와 같이 작성도 가능하다.
// @Schema({ timestamps: { createdAt: "createdAt", updatedAt: false } })
@Schema({ timestamps: { createdAt: "createdAt", updatedAt: "updatedAt" } })
class User{
@Prop({ default: new Date(), type: mongoose.Schema.Types.Date })
createdAt: Date;
@Prop({ default: new Date(), type: mongoose.Schema.Types.Date })
updatedAt: Date;
@Prop() // MongoDB에 들어갈 설정들을 적어준다.
userName: string; // 필드 이름: 타입(타입스크립트 타입)
@Prop()
password: string;
@Prop()
price: number;
// object를 저장하는 것도 가능하다.
@Prop({type:{
id: { required: true, type: mongoose.Schema.Types.ObjectId, ref: 'User' },
uuid: { required: true, type: String },
}})
friend: { id: 'sdf', uuid: 'asdf' };
}
// 위의 작성한 클래스를 바탕으로 Mongoose에서 사용하는 스키마 클래스를 만들어준다.
export const UserSchema = SchemaFactory.createForClass(User);
mongoose/nestJS는 클래스와 @Prop를 이용해 Schema를 정의하는데 @Prop안에는 다음과 같은 것들이 들어갈 수 있다.
@Prop({
require: true // 필수 필드 여부,
unique: true // 유니크
default: 0 // 기본값
type: mongoose.Schema.Types.Number // 몽고 DB에서 사용하는 필드별 타입
ref: 'ModelName' // 해당 필드가 다른 Collections를 참고 할 때 모델 이름을 적어줌
})
작성한 모델은 service에서 직접 사용하는게 아니라 ModelService를 이용해 간접적으로 사용한다. 즉 비즈니스 로직을 Model이 갖고 있는 Active data model 형태가 아닌 레포지토리나 데이터 매퍼 형태를 사용한다고 생각하면 된다. User 스키마가 있는 모듈에 아래와 같이 선언해준다. 아래에서 해당 스키마에 대한 CRUD 또는 메서드를 구현해주고 다른 서비스에서 사용한다.
// user.service.ts
import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { User, UserDocument } from './schemas/user.schema';
@Injectable()
export class UserService {
constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}
async getUser(userName: string): Promise<any> {
try {
const result = await this.userModel.findOne({ userName }).lean();
return result;
} catch (err) {
console.log('error...');
}
}
}
작성한 서비스를 외부에서 사용하기 위해선 아래처럼 MongooseModule에 모델을 등록해주고 exports 해줘야한다.
// user.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { User, UserSchema } from './schemas/user.schema';
@Module({
imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])],
controllers: [UserController],
providers: [UserService],
exports :[UserService],
// UserModule 외에서 사용하려면 export 해줘야한다.
})
export class UserModule {}
아래처럼 UserService가 있는 UserModule을 import해주면 해당 모듈 내부에서 사용 가능하다.
// item.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { ItemController } from './item.controller';
import { ItemService } from './item.service';
import { Item, ItemSchema } from './schemas/item.schema';
@Module({
imports: [
UserModule,
controllers: [],
providers: [],
})
export class ItemModule {}
다음 시간에는 스키마에 인덱스를 추가하는 방법에 대해 알아보도록 하겠다.
https://cocook.tistory.com/194
'Web Programming > NestJS' 카테고리의 다른 글
[NestJS] string을 mongo ObjectId로 변경하기 (0) | 2022.02.23 |
---|---|
[NestJS] mongoose 다른 이름으로 populate하기 (0) | 2022.02.14 |
[NestJS] 아직도 dotenv 사용함? (0) | 2022.02.03 |
[NestJS] Mongoose pre, post hook 설정 (0) | 2022.02.03 |
[NestJS] DTO(Data Transfer Object)를 이용한 Validation (0) | 2022.02.03 |