NestJS で MongoDB に接続するには 2 つの方法があります。1 つの方法は を使用してTypeORM
接続すること、もう 1 つの方法は を使用することですMongoose
。
このメモは主に記録保持に使用されますMongoose
。したがって、最初に必要な依存関係をインストールします。
npm i @nestjs/mongoose mongoose
インストールが完了したら、にAppModule
導入する必要がありますMongooseModule
。具体的な例としては以下のようなものがあります。
import databaseConfig from "./config/database.config";
import {
MongooseModule } from "@nestjs/mongoose";
@Module({
imports: [
MongooseModule.forRoot("mongodb://localhost:27017/managementsytem"),
CommodityModule,
],
})
export class AppModule {
}
MongooseModule.forRoot
の構成オブジェクトは、mongoose.connect()の構成オブジェクトと一致します。
モデルインジェクション
Mongoose の最も中心的なものはスキーマです。各スキーマは具体的な に変換されMongoDB集合
、スキーマはコレクション内のドキュメントの構造を定義します。
Mongoose では、モデルは主にデータの作成と最下層からのドキュメントの読み取りを担当します。
NestJs のパターンは、デコレーターを使用するか、Mongoose 自体を使用して手動で作成できます。デコレータを使用して作成されたパターンは、Mongoose 自体で手動で作成されたパターンよりもコード サイズとコードの可読性の点で優れています。したがって、パターンを作成するにはデコレータを使用することをお勧めします。具体的な例としては以下のようなものがあります。
import {
HydratedDocument } from "mongoose";
import {
Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
export type CommodityDocument = HydratedDocument<Commodity>;
@Schema()
export class Commodity {
@Prop()
name: string; // 商品名称
@Prop()
price: number; // 商品价格
@Prop()
stock: number; // 商品库存
@Prop([String])
tag: string; // 商品标签
}
export const CommoditySchema = SchemaFactory.createForClass(Commodity);
注:このクラス
を使用して、生のスキーマ定義を生成することもできます。DefinitionsFactory
これにより、提供したメタデータに基づいて生成されたスキーマ定義を手動で変更できます。モデルのフィールドが展開および削除される可能性がある場合、それを使用してDefinitionsFactory
スキーマのデータを維持できます。
@Schema()
デコレータの主な役割は、パターンを定義することです。上記の例では、Commodity
クラスは同じ名前のコレクションにマップされますMongoDB
が、MongoDB コレクション内の名前、つまり に「s」が追加されることに注意してくださいCommoditys
。このデコレータはオプションのパラメータを受け入れることができます。このパラメータは主に MongoDB モデル関連のパラメータを設定するために使用されます。具体的な内容は、ここで確認できます。
@Prop()
デコレータはドキュメント内のプロパティを定義します。たとえば、上記のスキーマ定義では、名前、価格、ラベルの 3 つのプロパティを定義します。これらのプロパティのスキーマ タイプは、TypeScript メタデータ (およびリフレクション) 機能を使用して自動的に推測できます。ただし、型を暗黙的に反映できない複雑なシナリオ (配列や入れ子になったオブジェクト構造など) では、次のように型を明示的に指定する必要があります。
@Prop([String])
tags: string[];
あるいは、 @Prop() デコレータはオプション オブジェクト引数を受け入れます (利用可能なオプションについて詳しくはこちらをご覧ください)。このようにして、プロパティが必須かどうかを示したり、デフォルト値を指定したり、プロパティを不変としてマークしたりできます。例えば:
@Prop({
required: true })
name: string;
モデルに別のプロパティがある場合は、@Prop()
デコレーターを使用できます。たとえば、Commodity には供給モデルが含まれているため、Commodity に特定のクラスと参照を追加する必要があります。具体的な例としては以下のようなものがあります。
import * as mongoose from 'mongoose';
import {
Supply } from '../owners/schemas/supply.schema';
@Prop({
type: mongoose.Schema.Types.ObjectId, ref: 'Supply' })
supply: Supply;
複数のプロバイダーがある場合、プロパティ構成は次のようになります。
@Prop({
type: [{
type: mongoose.Schema.Types.ObjectId, ref: 'Supply' }] })
supply: Supply[];
最後に、生のパターン定義をデコレーターに渡すこともできます。これは、たとえば、プロパティがクラスとして定義されていないネストされたオブジェクトを表す場合に便利です。これを行うには、次のように @nestjs/mongoose パッケージの raw() 関数を使用します。
@Prop(raw({
firstName: {
type: String },
lastName: {
type: String }
}))
details: Record<string, any>;
デコレータを使用したくない場合は、スキーマを手動で定義することもできます。例えば:
export const CommoditySchema = new mongoose.Schema({
name: String,
price: Number,
stock: Number,
tags: [String],
});
パターンを定義した後、Module
対応する中でそれを定義できます。具体的な例は次のとおりです。
@Module({
imports: [
ConfigModule,
MongooseModule.forFeature([
{
name: Commodity.name, schema: CommoditySchema },
]),
],
controllers: [CommodityController],
providers: [CommodityService],
})
export class CommodityModule {
}
MongooseModule は、現在のスコープに登録する必要があるモデルの定義など、モジュールを構成するための forFeature() メソッドを提供します。別のモジュールでもモデルを使用したい場合は、MongooseModule を CommodityModule のエクスポート パーツとして追加し、CommodityModule を別のモジュールにインポートするだけです。
スキーマを登録した後、 @InjectModel() デコレーターを使用して CommodityService に Commodity モデルを注入できます。
import {
Injectable } from "@nestjs/common";
import {
InjectModel } from "@nestjs/mongoose";
import {
Model } from "mongoose";
import {
Commodity } from "src/schemas/commodity.schemas";
@Injectable()
export class CommodityService {
constructor(
@InjectModel(Commodity.name) private commodityModel: Model<Commodity>
) {
}
create(commodity: Commodity) {
const createdCommdity = new this.commodityModel(commodity);
return createdCommdity.save();
}
}
Mongoose 接続オブジェクトを取得する
場合によっては、ネイティブ Mongoose Connection オブジェクトにアクセスする必要があるかもしれません。たとえば、接続オブジェクトに対してネイティブ 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) {
}
}
複数のデータベースを使用する
一部のプロジェクトでは、複数のデータベース接続が必要です。これは、このモジュールを通じても実現できます。複数の接続を使用するには、最初に接続を作成します。この場合、接続の名前付けは必須になります。具体的な例としては以下のようなものがあります。
import {
Module } from "@nestjs/common";
import {
CommodityModule } from "./module/commodity.module";
import {
AccountModule } from "./module/account.module";
import {
ConfigModule } from "@nestjs/config";
import configuration from "./config/configuration";
import databaseConfig from "./config/database.config";
import {
MongooseModule } from "@nestjs/mongoose";
@Module({
imports: [
ConfigModule.forRoot({
load: [configuration, databaseConfig],
cache: true,
}),
MongooseModule.forRoot("mongodb://localhost:27017/managementsytem", {
connectionName: "commodity",
}),
MongooseModule.forRoot("mongodb://localhost:27018/user", {
connectionName: "user",
}),
CommodityModule,
AccountModule,
],
})
export class AppModule {
}
**注意:** 名前のない接続や同じ名前の接続を複数作成しないでください。接続すると上書きされます。
複数のデータ リンクを設定する場合は、対応するモジュールのMongooseModule.forFeature()
関数を使用して、どのデータベースにリンクするかを宣言する必要があります。具体的な例としては以下のようなものがあります。
@Module({
imports: [
ConfigModule,
MongooseModule.forFeature(
[{
name: Commodity.name, schema: CommoditySchema }],
"commodity"
),
],
controllers: [CommodityController],
providers: [CommodityService],
exports: [CommodityService],
})
export class CommodityModule {
}
AppModule で複数のデータベースを宣言し、対応するモジュールでリンクされたデータベース名を宣言する場合、プロバイダーはデコレーターに 2 番目のパラメーターを追加する必要があります。具体的な例としては以下のようなものがあります。
@Injectable()
export class CatsService {
constructor(
@InjectModel(Commodity.name, "commodities")
private commodityModel: Model<Commodity>
) {
}
async create(commodity: Commodity) {
const createdCommdity = new this.commodityModel(commodity);
const _res = await createdCommdity.save();
return _res;
}
}
特定の接続に接続を挿入することもできます。
import {
Injectable } from "@nestjs/common";
import {
InjectConnection } from "@nestjs/mongoose";
import {
Connection } from "mongoose";
@Injectable()
export class CommodityService {
constructor(@InjectConnection("commodity") private connection: Connection) {
}
}
特定の接続をカスタム プロバイダー (ファクトリ プロバイダーなど) に挿入するには、getConnectionToken() 関数を使用し、接続名をパラメーターとして渡します。
{
provide: CommodityService,
useFactory: (commodityConnection: Connection) => {
return new CommodityService(commodityConnection);
},
inject: [getConnectionToken('commodity')],
}
Mongo フック (ミドルウェア)
Mongo フックの使用シナリオは、通常、独自のプラグインを作成するときに使用されます。
Mongo のミドルウェアはスキーマ レベルで指定されており、插件
記述に役立ちます。モデルのコンパイル後に pre() または post() を呼び出すと、Mongoose では機能しません。モデルを登録する前にフックを登録するには、MongooseModule の forFeatureAsync() メソッドをファクトリ プロバイダー (つまり useFactory) で使用します。この手法では、スキーマ オブジェクトにアクセスし、pre() メソッドまたは post() メソッドを使用してスキーマにフックを登録します。具体的な例としては以下のようなものがあります。
@Module({
imports: [
MongooseModule.forFeatureAsync([
{
name: Commodity.name,
useFactory: () => {
const schema = CommoditySchema;
schema.pre("save", function () {
console.log("Hello from pre save");
});
return schema;
},
},
]),
],
})
export class AppModule {
}
他のファクトリ プロバイダーと同様に、ファクトリ関数は非同期にすることができ、インジェクションによって依存関係を注入することができます。
@Module({
imports: [
MongooseModule.forFeatureAsync([
{
name: Commodity.name,
imports: [ConfigModule],
useFactory: (configService: ConfigService) => {
const schema = CommoditySchema;
schema.pre('save', function() {
console.log(
`${
configService.get('APP_NAME')}: Hello from pre save`,
),
});
return schema;
},
inject: [ConfigService],
},
]),
],
})
export class AppModule {
}
ミドルウェアでのプラグインの使用
特定のスキーマのプラグインを登録するには、forFeatureAsync() メソッドを使用します。
@Module({
imports: [
MongooseModule.forFeatureAsync([
{
name: Commodity.name,
useFactory: () => {
const schema = CommoditySchema;
schema.plugin(require("mongoose-autopopulate"));
return schema;
},
},
]),
],
})
export class AppModule {
}
すべてのモードのプラグインを一度に登録するには、Connection オブジェクトの .plugin() メソッドを呼び出します。モデルを作成する前に接続にアクセスする必要があります。これには、接続ファクトリーを使用します。
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 {
}
識別子
ディスクリミネーターはスキーマ継承メカニズムです。これにより、同じ基盤となる MongoDB コレクション上に、重複するスキーマを持つ複数のモデルを配置できるようになります。この機能は、継承関係を形成するモデルに相当する。
単一のコレクション内のさまざまな種類のイベントを追跡するとします。各イベントにはタイムスタンプが付いています。
@Schema({
discriminatorKey: "kind" })
export class Event {
@Prop({
type: String,
required: true,
enum: [ClickedLinkEvent.name, SignUpEvent.name],
})
kind: string;
@Prop({
type: Date, required: true })
time: Date;
}
export const EventSchema = SchemaFactory.createForClass(Event);
SignedUpEvent インスタンスと ClickedLinkEvent インスタンスは、汎用イベントと同じコレクションに保存されます。
ここで、ClickedLinkEvent クラスを次のように定義しましょう。
@Schema()
export class ClickedLinkEvent {
kind: string;
time: Date;
@Prop({
type: String, required: true })
url: string;
}
export const ClickedLinkEventSchema =
SchemaFactory.createForClass(ClickedLinkEvent);
そして SignUpEvent クラス:
@Schema()
export class SignUpEvent {
kind: string;
time: Date;
@Prop({
type: String, required: true })
user: string;
}
export const SignUpEventSchema = SchemaFactory.createForClass(SignUpEvent);
これが完了したら、discriminator オプションを使用して、指定されたスキーマの discriminator を登録します。MongooseModule.forFeature および MongooseModule.forFeatureAsync で動作します。
import {
Module } from "@nestjs/common";
import {
MongooseModule } from "@nestjs/mongoose";
@Module({
imports: [
MongooseModule.forFeature([
{
name: Event.name,
schema: EventSchema,
discriminators: [
{
name: ClickedLinkEvent.name, schema: ClickedLinkEventSchema },
{
name: SignUpEvent.name, schema: SignUpEventSchema },
],
},
]),
],
})
export class EventsModule {
}
上記のコードを完成すると、実際にはモデル間の関係は次のようになります。
非同期構成
モジュール オプションを静的にではなく非同期で渡す必要がある場合は、forRootAsync() メソッドを使用します。ほとんどの動的モジュールと同様に、Nest は非同期構成を処理するためのいくつかの手法を提供します。
1 つの手法は、ファクトリー関数を使用することです。
MongooseModule.forRootAsync({
useFactory: () => ({
uri: "mongodb://localhost/nest",
}),
});
他のファクトリ プロバイダーと同様に、ファクトリ関数は非同期にすることができ、インジェクションによって依存関係を注入することができます。
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
uri: configService.get<string>("MONGODB_URI"),
}),
inject: [ConfigService],
});
あるいは、次のように、ファクトリの代わりにクラスを使用して MongooseModule を構成することもできます。
MongooseModule.forRootAsync({
useClass: MongooseConfigService,
});
上記の構造は、MongooseModule で MongooseConfigService をインスタンス化し、それを使用して必要なオプション オブジェクトを作成します。この例では、MongooseConfigService は以下に示すように MongooseOptionsFactory インターフェイスを実装する必要があることに注意してください。MongooseModule は、提供されたクラスのインスタンス化で createMongooseOptions() メソッドを呼び出します。
@Injectable()
export class MongooseConfigService implements MongooseOptionsFactory {
createMongooseOptions(): MongooseModuleOptions {
return {
uri: "mongodb://localhost/nest",
};
}
}
MongooseModule 内にプライベート コピーを作成するのではなく、既存のオプション プロバイダーを再利用する場合は、useExisting 構文を使用します。
MongooseModule.forRootAsync({
imports: [ConfigModule],
useExisting: ConfigService,
});