MongoDBとの連携
本セクションでは後者について、専用の
@nestjs/mongoose
パッケージを使いながら説明します。npm install --save @nestjs/mongoose mongoose
インストールが終了したら、
MongooseModule
をルートの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()
と同じ設定オブジェクトを受け取ります。各SchemaはMongoDBのコレクションに対応し、そのコレクション内のドキュメントの形を定義します。Schemaはモデルの定義に使われます。モデルはMongoDBからドキュメントを生成したり読み込んだりする役割を果たします。
SchemaはNestJSデコレータで作れますし、Mongoose自身で手動で作成できます。デコレータを使ってスキーマを作成すると、定型文が貼ってコード全体の読みやすさが向上します。
CatSchema
を定義してみましょう。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);
メモ
DefinitionsFactory
クラス(@nestjs/mongoose
にあるパッケージ)を使って生のSchema定義を生成できることには十分に注意してください。これによって、提供されたメタデータに基づいて生成されたSchema定義を手動で修正できます。これは、デコレータですべてを表現するのが難しいような、ある種のエッジケースに便利です。
@Schema()
デコレータは、クラスをSchema定義としてマークします。これはCat
クラスを同じ名前のMongoDBコレクションに対応させますが、最後に"s"を追加します。このデコレータが受けるオプションの引数は一つで、スキーマオプションオブジェクトです。これは、通常mongoose.Schema
クラスのコンストラクタの第二引数に渡すオブジェクトだと考えてください。@Props()
デコレータは、ドキュメント内のプロパティを定義します。例えば、上記のスキーマ定義ではname
、age
、breed
という3つのプロパティを定義しています。これらのプロパティのSchemaの型は、TypeScriptのメタデータの機能によって自動的に推測されます。しかし、型を暗黙的に反映できないようなより複雑なシナリオ(例えば、配列やネストしたオブジェクト構造)では、以下のように型を明示的に示す必要があります。@Prop([String])
tags: string[];
あるいは、
@Prop()
デコレータにoption
オブジェクトの引数を指定することもできます。これで、そのプロパティが必須かどうかを指定したり、不変であるとマークしたりできます。@Prop({ required: true })
name: string;
他のモデルとの関係を指定した場合は、
@Prop()
デコレータを使用できます。例えば、Cat
がowners
という別のコレクションに格納されているOwner
を持っている場合、プロパティはtype
とref
を持つ必要があります。import * as mongoose from 'mongoose';
import { Owner } from '../owners/schemas/owner.schema';
// クラス定義の内部に書く
// type:やり取りするデータの型, ref:データの説明
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Owner' })
owner: Owner;
複数の所有者(
owner
)がいる場合、プロパティ構成は以下のようになります。@Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Owner' }] })
owner: Owner[];
最後に、生のSchema定義もデコレータに渡すことができます。これは、例えばあるプロパティがクラスとして定義されていないネストされたオブジェクトを表している場合に便利です。この場合、
@nestjs/mongoose
パッケージのraw()
関数を以下のようにします。@Prop(raw({
firstName: { type: String },
lastName: { type: String }
}))
details: Record<string, any>;
また、デコレータを使わずにSchemaを定義したい場合は手動で設定できます。
export const CatSchema = new mongoose.Schema({
name: String,
age: Number,
breed: String,
});
cat.schema
ファイルは、cats
ディレクトリの中のフォルダに存在し、そこでCatsModule
も定義しています。Schemaファイルは好きな場所に保存できますが、関連するドメインオブジェクトの近く、適切なModuleディレクトリに保存します。CatsModule
を見てみましょう。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()
メソッドを提供しており、現在のスコープでどのモデルを登録するかの定義など、モジュールの設定を行うことができます。他のモジュールでもモデルを使いたい場合は、CatsModule
のexports
セクションにMongooseModule
を追加して、他のモジュールでCatsModule
をインポートしてください。スキーマを登録したら、
@InjectModel()
デコレータを使ってCatsService
にCat
モデルをインジェクトできます。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.name) 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();
}
}
Mongoose Connectionをインジェクトするためには、以下のように
@InjectConnection()
デコレータを使います。import { Injectable } from '@nestjs/common';
import { InjectConnection } from '@nestjs/mongoose';
import { Connection } from 'mongoose';
@Injectable()
export class CatsService {
constructor(@InjectConnection() private connection: Connection) {}
}