Nest支持兩種與 MongoDB 數(shù)據(jù)庫集成的方式。既使用內(nèi)置的TypeORM 提供的 MongoDB 連接器,或使用最流行的 MongoDB 對(duì)象建模工具 Mongoose。在本章后續(xù)描述中我們使用專用的@nestjs/mongoose包。
首先,我們需要安裝所有必需的依賴項(xiàng):
$ npm install --save @nestjs/mongoose mongoose
安裝過程完成后,我們可以將其 MongooseModule 導(dǎo)入到根目錄 AppModule 中。
app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [MongooseModule.forRoot('mongodb://localhost/nest')],
})
export class AppModule {}
該 forRoot() 和 mongoose 包中的 mongoose.connect() 一樣的參數(shù)對(duì)象。參見。
在Mongoose中,一切都源于 Scheme,每個(gè) Schema 都會(huì)映射到 MongoDB 的一個(gè)集合,并定義集合內(nèi)文檔的結(jié)構(gòu)。Schema 被用來定義模型,而模型負(fù)責(zé)從底層創(chuàng)建和讀取 MongoDB 的文檔。
Schema 可以用 NestJS 內(nèi)置的裝飾器來創(chuàng)建,或者也可以自己動(dòng)手使用 Mongoose的常規(guī)方式。使用裝飾器來創(chuàng)建 Schema 會(huì)極大大減少引用并且提高代碼的可讀性。
我們先定義CatSchema:
schemas/cat.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
export type CatDocument = Cat & Document;
@Schema()
export class Cat extends Document {
@Prop()
name: string;
@Prop()
age: number;
@Prop()
breed: string;
}
export const CatSchema = SchemaFactory.createForClass(Cat);
注意你也可以通過使用 DefinitionsFactory 類(可以從 @nestjs/mongoose 導(dǎo)入)來生成一個(gè)原始 Schema ,這將允許你根據(jù)被提供的元數(shù)據(jù)手動(dòng)修改生成的 Schema 定義。這對(duì)于某些很難用裝飾器體現(xiàn)所有的極端例子非常有用。
@Schema 裝飾器標(biāo)記一個(gè)類作為Schema 定義,它將我們的 Cat 類映射到 MongoDB 同名復(fù)數(shù)的集合 Cats,這個(gè)裝飾器接受一個(gè)可選的 Schema 對(duì)象。將它想象為那個(gè)你通常會(huì)傳遞給 mongoose.Schema 類的構(gòu)造函數(shù)的第二個(gè)參數(shù)(例如, new mongoose.Schema(_, options)))。 更多可用的 Schema 選項(xiàng)可以 看這里。
@Prop 裝飾器在文檔中定義了一個(gè)屬性。舉個(gè)例子,在上面的 Schema 定義中,我們定義了三個(gè)屬性,分別是:name ,age 和 breed。得益于 TypeScript 的元數(shù)據(jù)(還有反射),這些屬性的 Schema類型會(huì)被自動(dòng)推斷。然而在更復(fù)雜的場(chǎng)景下,有些類型例如對(duì)象和嵌套數(shù)組無法正確推斷類型,所以我們要向下面一樣顯式的指出。
@Prop([String])
tags: string[];
另外的 @Prop 裝飾器接受一個(gè)可選的參數(shù),通過這個(gè),你可以指示這個(gè)屬性是否是必須的,是否需要默認(rèn)值,或者是標(biāo)記它作為一個(gè)常量,下面是例子:
@Prop({ required: true })
name: string;
最后的,原始 Schema 定義也可以被傳遞給裝飾器。這也非常有用,舉個(gè)例子,一個(gè)屬性體現(xiàn)為一個(gè)嵌套對(duì)象而不是一個(gè)定義的類。要使用這個(gè),需要從像下面一樣從 @nestjs/mongoose 包導(dǎo)入 raw()。
@Prop(raw({
firstName: { type: String },
lastName: { type: String }
}))
details: Record<string, any>;
或者,如果你不喜歡使用裝飾器,你可以使用 mongoose.Schema 手動(dòng)定義一個(gè) Schema。下面是例子:
schemas/cat.schema.ts
import * as mongoose from 'mongoose';
export const CatSchema = new mongoose.Schema({
name: String,
age: Number,
breed: String,
});
該 cat.schema 文件在 cats 目錄下。這個(gè)目錄包含了和 CatsModule模塊有關(guān)的所有文件。你可以決定在哪里保存Schema文件,但我們推薦在他們的域中就近創(chuàng)建,即在相應(yīng)的模塊目錄中。
我們來看看CatsModule:
cats.module.ts
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 {}
MongooseModule提供了forFeature()方法來配置模塊,包括定義哪些模型應(yīng)該注冊(cè)在當(dāng)前范圍中。如果你還想在另外的模塊中使用這個(gè)模型,將MongooseModule添加到CatsModule的exports部分并在其他模塊中導(dǎo)入CatsModule。
注冊(cè)Schema后,可以使用 @InjectModel() 裝飾器將 Cat 模型注入到 CatsService 中:
cats.service.ts
import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cat, CatDocument } from './schemas/cat.schema';
import { CreateCatDto } from './dto/create-cat.dto';
@Injectable()
export class CatsService {
constructor(@InjectModel('Cat') private catModel: Model<CatDocument>) {}
async create(createCatDto: CreateCatDto): Promise<Cat> {
const createdCat = new this.catModel(createCatDto);
return createdCat.save();
}
async findAll(): Promise<Cat[]> {
return this.catModel.find().exec();
}
}
有時(shí)你可能需要連接原生的Mongoose 連接對(duì)象,你可能在連接對(duì)象中想使用某個(gè)原生的 API。你可以使用如下的@InjectConnection()裝飾器來注入 Mongoose 連接。
import { Injectable } from '@nestjs/common';
import { InjectConnection } from '@nestjs/mongoose';
import { Connection } from 'mongoose';
@Injectable()
export class CatsService {
constructor(@InjectConnection() private connection: Connection) {}
}
有的項(xiàng)目需要多數(shù)據(jù)庫連接,可以在這個(gè)模塊中實(shí)現(xiàn)。要使用多連接,首先要?jiǎng)?chuàng)建連接,在這種情況下,連接必須**要有名稱。
app.module.ts
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 {}
你不能在沒有名稱的情況下使用多連接,也不能對(duì)多連接使用同一個(gè)名稱,否則會(huì)被覆蓋掉。
在設(shè)置中,要告訴MongooseModule.forFeature()方法應(yīng)該使用哪個(gè)連接。
@Module({
imports: [MongooseModule.forFeature([{ name: 'Cat', schema: CatSchema }], 'cats')],
})
export class AppModule {}
也可以向一個(gè)給定的連接中注入Connection。
import { Injectable } from '@nestjs/common';
import { InjectConnection } from '@nestjs/mongoose';
import { Connection } from 'mongoose';
@Injectable()
export class CatsService {
constructor(@InjectConnection('cats') private connection: Connection) {}
}
中間件(也被稱作預(yù)處理(pre)和后處理(post)鉤子)是在執(zhí)行異步函數(shù)時(shí)傳遞控制的函數(shù)。中間件是針對(duì)Schema層級(jí)的,在寫插件(源碼)時(shí)非常有用。在 Mongoose 編譯完模型后使用pre()或post()不會(huì)起作用。要在模型注冊(cè)前注冊(cè)一個(gè)鉤子,可以在使用一個(gè)工廠提供者(例如 useFactory)是使用MongooseModule中的forFeatureAsync()方法。使用這一技術(shù),你可以訪問一個(gè) Schema 對(duì)象,然后使用pre()或post()方法來在那個(gè) schema 中注冊(cè)一個(gè)鉤子。示例如下:
@Module({
imports: [
MongooseModule.forFeatureAsync([
{
name: 'Cat',
useFactory: () => {
const schema = CatsSchema;
schema.pre('save', () => console.log('Hello from pre save'));
return schema;
},
},
]),
],
})
export class AppModule {}
和其他工廠提供者一樣,我們的工廠函數(shù)是異步的,可以通過inject注入依賴。
@Module({
imports: [
MongooseModule.forFeatureAsync([
{
name: 'Cat',
imports: [ConfigModule],
useFactory: (configService: ConfigService) => {
const schema = CatsSchema;
schema.pre('save', () => console.log(`${configService.get<string>('APP_NAME')}: Hello from pre save`));
return schema;
},
inject: [ConfigService],
},
]),
],
})
export class AppModule {}
要向給定的 schema 中注冊(cè)插件,可以使用forFeatureAsync()方法。
@Module({
imports: [
MongooseModule.forFeatureAsync([
{
name: 'Cat',
useFactory: () => {
const schema = CatsSchema;
schema.plugin(require('mongoose-autopopulate'));
return schema;
},
},
]),
],
})
export class AppModule {}
要向所有 schema 中立即注冊(cè)一個(gè)插件,調(diào)用Connection對(duì)象中的.plugin()方法。你可以在所有模型創(chuàng)建前訪問連接。使用connectionFactory來實(shí)現(xiàn):
app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/test', {
connectionFactory: (connection) => {
connection.plugin(require('mongoose-autopopulate'));
return connection;
},
}),
],
})
export class AppModule {}
在單元測(cè)試我們的應(yīng)用程序時(shí),我們通常希望避免任何數(shù)據(jù)庫連接,使我們的測(cè)試套件獨(dú)立并盡可能快地執(zhí)行它們。但是我們的類可能依賴于從連接實(shí)例中提取的模型。如何處理這些類呢?解決方案是創(chuàng)建模擬模型。
為了簡(jiǎn)化這一過程,@nestjs/mongoose 包公開了一個(gè) getModelToken() 函數(shù),該函數(shù)根據(jù)一個(gè) token 名稱返回一個(gè)準(zhǔn)備好的[注入token](https://docs.nestjs.com/fundamentals/custom-providers#di-fundamentals)。使用此 token,你可以輕松地使用任何標(biāo)準(zhǔn)自定義提供者技術(shù),包括 useClass、useValue 和 useFactory。例如:
@Module({
providers: [
CatsService,
{
provide: getModelToken('Cat'),
useValue: catModel,
},
],
})
export class CatsModule {}
在本例中,每當(dāng)任何使用者使用 @InjectModel() 裝飾器注入模型時(shí),都會(huì)提供一個(gè)硬編碼的 Model<Cat> (對(duì)象實(shí)例)。
通常,您可能希望異步傳遞模塊選項(xiàng),而不是事先傳遞它們。在這種情況下,使用 forRootAsync() 方法,Nest提供了幾種處理異步數(shù)據(jù)的方法。
第一種可能的方法是使用工廠函數(shù):
MongooseModule.forRootAsync({
useFactory: () => ({
uri: 'mongodb://localhost/nest',
}),
});
與其他工廠提供程序一樣,我們的工廠函數(shù)可以是異步的,并且可以通過注入注入依賴。
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
uri: configService.getString('MONGODB_URI'),
}),
inject: [ConfigService],
});
或者,您可以使用類而不是工廠來配置 MongooseModule,如下所示:
MongooseModule.forRootAsync({
useClass: MongooseConfigService,
});
上面的構(gòu)造在 MongooseModule中實(shí)例化了 MongooseConfigService,使用它來創(chuàng)建所需的 options 對(duì)象。注意,在本例中,MongooseConfigService 必須實(shí)現(xiàn) MongooseOptionsFactory 接口,如下所示。 MongooseModule 將在提供的類的實(shí)例化對(duì)象上調(diào)用 createMongooseOptions() 方法。
@Injectable()
class MongooseConfigService implements MongooseOptionsFactory {
createMongooseOptions(): MongooseModuleOptions {
return {
uri: 'mongodb://localhost/nest',
};
}
}
為了防止 MongooseConfigService 內(nèi)部創(chuàng)建 MongooseModule 并使用從不同模塊導(dǎo)入的提供程序,您可以使用 useExisting 語法。
MongooseModule.forRootAsync({
imports: [ConfigModule],
useExisting: ConfigService,
});
一個(gè)可用的示例見這里。
更多建議: