スキーマ
まだお済みでない場合は、Mongooseの動作について理解するために、クイックスタート を読んでみてください。7.x から 8.x に移行する場合は、移行ガイド を読んでください。
スキーマの定義
Mongooseでは、すべてがスキーマから始まります。各スキーマはMongoDBコレクションにマッピングされ、そのコレクション内のドキュメントの構造を定義します。
import mongoose from 'mongoose';
const { Schema } = mongoose;
const blogSchema = new Schema({
title: String, // String is shorthand for {type: String}
author: String,
body: String,
comments: [{ body: String, date: Date }],
date: { type: Date, default: Date.now },
hidden: Boolean,
meta: {
votes: Number,
favs: Number
}
});
後で追加のキーを追加する場合は、Schema#add メソッドを使用します。
上記のコードblogSchema
の各キーは、ドキュメント内のプロパティを定義し、関連付けられたSchemaTypeにキャストされます。たとえば、title
プロパティはString SchemaTypeに、date
プロパティはDate
SchemaTypeにキャストされます。
上記のように、プロパティに型のみが必要な場合は、省略記法を使用して指定できます(上記のtitle
プロパティとdate
プロパティを比較してください)。
キーには、上記のmeta
プロパティのように、さらにキー/型の定義を含むネストされたオブジェクトを割り当てることもできます。これは、キーの値がtype
プロパティを持たないPOJOである場合に発生します。
このような場合、Mongooseはツリーの葉(上記のmeta.votes
とmeta.favs
など)に対してのみ実際のスキーマパスを作成し、ブランチには実際のパスがありません。この副作用として、上記のmeta
は独自のバリデーションを持つことができません。ツリーの上位でバリデーションが必要な場合は、ツリーの上位にパスを作成する必要があります。これを行う方法の詳細については、サブドキュメントセクションを参照してください。また、MixedのSchemaTypesガイドのサブセクションで、いくつかの注意点を確認してください。
許可されるSchemaTypesは次のとおりです。
スキーマは、ドキュメントの構造とプロパティのキャストを定義するだけでなく、ドキュメントのインスタンスメソッド、スタティックモデルメソッド、複合インデックス、ミドルウェアと呼ばれるドキュメントライフサイクルフックも定義します。
モデルの作成
スキーマ定義を使用するには、blogSchema
を操作できるモデルに変換する必要があります。そのためには、mongoose.model(modelName, schema)
に渡します。
const Blog = mongoose.model('Blog', blogSchema);
// ready to go!
ID
デフォルトでは、Mongooseはスキーマに_id
プロパティを追加します。
const schema = new Schema();
schema.path('_id'); // ObjectId { ... }
自動的に追加された_id
プロパティを使用して新しいドキュメントを作成すると、Mongooseはドキュメントに新しいObjectId型の_id
を作成します。
const Model = mongoose.model('Test', schema);
const doc = new Model();
doc._id instanceof mongoose.Types.ObjectId; // true
独自の_id
でMongooseのデフォルトの_id
を上書きすることもできます。ただし注意してください。Mongooseは、_id
を持たない最上位のドキュメントの保存を拒否するため、独自の_id
パスを定義する場合は、_id
の設定を担当します。
const schema = new Schema({
_id: Number // <-- overwrite Mongoose's default `_id`
});
const Model = mongoose.model('Test', schema);
const doc = new Model();
await doc.save(); // Throws "document must have an _id before saving"
doc._id = 1;
await doc.save(); // works
Mongooseはサブドキュメントにも_id
プロパティを追加します。サブドキュメントの_id
プロパティを無効にするには、次のようにします。Mongooseは、_id
プロパティを持たないサブドキュメントの保存を許可します。
const nestedSchema = new Schema(
{ name: String },
{ _id: false } // <-- disable `_id`
);
const schema = new Schema({
subdoc: nestedSchema,
docArray: [nestedSchema]
});
const Test = mongoose.model('Test', schema);
// Neither `subdoc` nor `docArray.0` will have an `_id`
await Test.create({
subdoc: { name: 'test 1' },
docArray: [{ name: 'test 2' }]
});
または、次の構文を使用して_id
を無効にすることもできます。
const nestedSchema = new Schema({
_id: false, // <-- disable _id
name: String
});
インスタンスメソッド
Models
のインスタンスはドキュメントです。ドキュメントには、独自の組み込みインスタンスメソッドが多数あります。独自のドキュメントインスタンスメソッドを定義することもできます。
// define a schema
const animalSchema = new Schema({ name: String, type: String },
{
// Assign a function to the "methods" object of our animalSchema through schema options.
// By following this approach, there is no need to create a separate TS type to define the type of the instance functions.
methods: {
findSimilarTypes(cb) {
return mongoose.model('Animal').find({ type: this.type }, cb);
}
}
});
// Or, assign a function to the "methods" object of our animalSchema
animalSchema.methods.findSimilarTypes = function(cb) {
return mongoose.model('Animal').find({ type: this.type }, cb);
};
これで、すべてのanimal
インスタンスでfindSimilarTypes
メソッドを使用できるようになりました。
const Animal = mongoose.model('Animal', animalSchema);
const dog = new Animal({ type: 'dog' });
dog.findSimilarTypes((err, dogs) => {
console.log(dogs); // woof
});
- デフォルトのMongooseドキュメントメソッドを上書きすると、予期しない結果になる可能性があります。詳細についてはこちらを参照してください。
- 上記の例では、インスタンスメソッドを保存するために
Schema.methods
オブジェクトを直接使用しています。 こちらで説明されているように、Schema.method()
ヘルパーを使用することもできます。 - ES6アロー関数(
=>
)を使用してメソッドを宣言しないでください。アロー関数はthis
のバインディングを明示的に防止するため、メソッドはドキュメントにアクセスできず、上記の例は機能しません。
スタティックメソッド
モデルにスタティック関数を追加することもできます。スタティックを追加するには、3つの同等の方法があります。
- スキーマコンストラクターの第2引数(
statics
)に関数プロパティを追加します。 schema.statics
に関数プロパティを追加します。Schema#static()
関数を呼び出します。
// define a schema
const animalSchema = new Schema({ name: String, type: String },
{
// Assign a function to the "statics" object of our animalSchema through schema options.
// By following this approach, there is no need to create a separate TS type to define the type of the statics functions.
statics: {
findByName(name) {
return this.find({ name: new RegExp(name, 'i') });
}
}
});
// Or, Assign a function to the "statics" object of our animalSchema
animalSchema.statics.findByName = function(name) {
return this.find({ name: new RegExp(name, 'i') });
};
// Or, equivalently, you can call `animalSchema.static()`.
animalSchema.static('findByBreed', function(breed) { return this.find({ breed }); });
const Animal = mongoose.model('Animal', animalSchema);
let animals = await Animal.findByName('fido');
animals = animals.concat(await Animal.findByBreed('Poodle'));
ES6アロー関数(=>
)を使用してスタティックを宣言しないでください。アロー関数はthis
のバインディングを明示的に防止するため、this
の値のために上記の例は機能しません。
クエリヘルパー
クエリヘルパー関数も追加できます。これは、インスタンスメソッドに似ていますが、Mongooseクエリ用です。クエリヘルパーメソッドを使用すると、Mongooseのチェーン可能なクエリビルダーAPIを拡張できます。
// define a schema
const animalSchema = new Schema({ name: String, type: String },
{
// Assign a function to the "query" object of our animalSchema through schema options.
// By following this approach, there is no need to create a separate TS type to define the type of the query functions.
query: {
byName(name) {
return this.where({ name: new RegExp(name, 'i') });
}
}
});
// Or, Assign a function to the "query" object of our animalSchema
animalSchema.query.byName = function(name) {
return this.where({ name: new RegExp(name, 'i') });
};
const Animal = mongoose.model('Animal', animalSchema);
Animal.find().byName('fido').exec((err, animals) => {
console.log(animals);
});
Animal.findOne().byName('fido').exec((err, animal) => {
console.log(animal);
});
インデックス
MongoDBはセカンダリインデックスをサポートしています。Mongooseでは、これらのインデックスをSchema
のパスレベルまたはスキーマレベルで定義します。複合インデックスを作成する場合は、スキーマレベルでインデックスを定義する必要があります。
const animalSchema = new Schema({
name: String,
type: String,
tags: { type: [String], index: true } // path level
});
animalSchema.index({ name: 1, type: -1 }); // schema level
他のインデックスオプションについては、SchemaType#index()を参照してください。
アプリケーションの起動時に、Mongooseはスキーマで定義された各インデックスに対してcreateIndex
を自動的に呼び出します。Mongooseは各インデックスに対して順番にcreateIndex
を呼び出し、すべてのcreateIndex
呼び出しが成功した場合、またはエラーが発生した場合は、モデルで 'index' イベントを発行します。開発には便利ですが、インデックスの作成はデータベースのパフォーマンスに大きな影響を与える可能性があるため、本番環境ではこの動作を無効にすることをお勧めします。スキーマのautoIndex
オプションをfalse
に設定するか、接続でグローバルにautoIndex
オプションをfalse
に設定することで、動作を無効にします。
mongoose.connect('mongodb://user:pass@127.0.0.1:port/database', { autoIndex: false });
// or
mongoose.createConnection('mongodb://user:pass@127.0.0.1:port/database', { autoIndex: false });
// or
mongoose.set('autoIndex', false);
// or
animalSchema.set('autoIndex', false);
// or
new Schema({ /* ... */ }, { autoIndex: false });
インデックスの構築が完了した場合、またはエラーが発生した場合は、モデルでindex
イベントが発行されます。
// Will cause an error because mongodb has an _id index by default that
// is not sparse
animalSchema.index({ _id: 1 }, { sparse: true });
const Animal = mongoose.model('Animal', animalSchema);
Animal.on('index', error => {
// "_id index cannot be sparse"
console.log(error.message);
});
Model#ensureIndexesメソッドも参照してください。
仮想プロパティ
仮想プロパティは、取得および設定できますが、MongoDBに永続化されないドキュメントプロパティです。ゲッターは、フィールドのフォーマットまたは組み合わせに役立ち、セッターは、単一の値を複数の値に分解して保存するのに役立ちます。
// define a schema
const personSchema = new Schema({
name: {
first: String,
last: String
}
});
// compile our model
const Person = mongoose.model('Person', personSchema);
// create a document
const axl = new Person({
name: { first: 'Axl', last: 'Rose' }
});
人のフルネームを出力したいとします。自分で行うことができます。
console.log(axl.name.first + ' ' + axl.name.last); // Axl Rose
しかし、毎回氏名と名を連結するのは面倒です。そして、アクセント記号を削除するなど、名前に対して追加の処理を行いたい場合はどうすればよいでしょうか?仮想プロパティゲッターを使用すると、MongoDBに永続化されないfullName
プロパティを定義できます。
// That can be done either by adding it to schema options:
const personSchema = new Schema({
name: {
first: String,
last: String
}
}, {
virtuals: {
fullName: {
get() {
return this.name.first + ' ' + this.name.last;
}
}
}
});
// Or by using the virtual method as following:
personSchema.virtual('fullName').get(function() {
return this.name.first + ' ' + this.name.last;
});
これで、fullName
プロパティにアクセスするたびに、Mongooseはゲッター関数を呼び出します。
console.log(axl.fullName); // Axl Rose
toJSON()
またはtoObject()
を使用する場合、Mongooseはデフォルトで仮想プロパティを含めません。仮想プロパティを含めるには、toJSON()
またはtoObject()
に{ virtuals: true }
を渡します。
// Convert `doc` to a POJO, with virtuals attached
doc.toObject({ virtuals: true });
// Equivalent:
doc.toJSON({ virtuals: true });
上記のtoJSON()
に関する注意点は、JSON.stringify()
をMongooseドキュメントで呼び出した出力にも適用されます。JSON.stringify()
はtoJSON()
を呼び出すためです。JSON.stringify()
の出力に仮想プロパティを含めるには、JSON.stringify()
を呼び出す前にドキュメントでtoObject({ virtuals: true })
を呼び出すか、スキーマでtoJSON: { virtuals: true }
オプションを設定します。
// Explicitly add virtuals to `JSON.stringify()` output
JSON.stringify(doc.toObject({ virtuals: true }));
// Or, to automatically attach virtuals to `JSON.stringify()` output:
const personSchema = new Schema({
name: {
first: String,
last: String
}
}, {
toJSON: { virtuals: true } // <-- include virtuals in `JSON.stringify()`
});
仮想プロパティにカスタムセッターを追加して、fullName
仮想プロパティを介して名前と姓の両方を設定することもできます。
// Again that can be done either by adding it to schema options:
const personSchema = new Schema({
name: {
first: String,
last: String
}
}, {
virtuals: {
fullName: {
get() {
return this.name.first + ' ' + this.name.last;
},
set(v) {
this.name.first = v.substr(0, v.indexOf(' '));
this.name.last = v.substr(v.indexOf(' ') + 1);
}
}
}
});
// Or by using the virtual method as following:
personSchema.virtual('fullName').
get(function() {
return this.name.first + ' ' + this.name.last;
}).
set(function(v) {
this.name.first = v.substr(0, v.indexOf(' '));
this.name.last = v.substr(v.indexOf(' ') + 1);
});
axl.fullName = 'William Rose'; // Now `axl.name.first` is "William"
仮想プロパティセッターは、他のバリデーションの前に適用されます。そのため、上記の例は、first
とlast
の名前フィールドが必須の場合でも機能します。
クエリおよびフィールド選択の一部として機能するのは、仮想プロパティ以外のプロパティのみです。仮想プロパティはMongoDBに保存されないため、それらを使用してクエリを実行することはできません。
エイリアス
エイリアスは、ゲッターとセッターがシームレスに別のプロパティを取得および設定する特定の種類の仮想プロパティです。これはネットワーク帯域幅を節約するのに役立つため、データベースに保存されている短いプロパティ名を、コードの可読性のために長い名前に変換できます。
const personSchema = new Schema({
n: {
type: String,
// Now accessing `name` will get you the value of `n`, and setting `name` will set the value of `n`
alias: 'name'
}
});
// Setting `name` will propagate to `n`
const person = new Person({ name: 'Val' });
console.log(person); // { n: 'Val' }
console.log(person.toObject({ virtuals: true })); // { n: 'Val', name: 'Val' }
console.log(person.name); // "Val"
person.name = 'Not Val';
console.log(person); // { n: 'Not Val' }
ネストされたパスにもエイリアスを宣言できます。サブドキュメントとネストされたスキーマを使用する方が簡単ですが、nested.myProp
をエイリアスとして使用する限り、ネストされたパスのエイリアスをインラインで宣言することもできます。
const childSchema = new Schema({
n: {
type: String,
alias: 'name'
}
}, { _id: false });
const parentSchema = new Schema({
// If in a child schema, alias doesn't need to include the full nested path
c: childSchema,
name: {
f: {
type: String,
// Alias needs to include the full nested path if declared inline
alias: 'name.first'
}
}
});
オプション
スキーマには、コンストラクターまたはset
メソッドに渡すことができるいくつかの構成可能なオプションがあります。
new Schema({ /* ... */ }, options);
// or
const schema = new Schema({ /* ... */ });
schema.set(option, value);
有効なオプション
- autoIndex
- autoCreate
- bufferCommands
- bufferTimeoutMS
- capped
- collection
- discriminatorKey
- excludeIndexes
- id
- _id
- minimize
- read
- writeConcern
- shardKey
- statics
- strict
- strictQuery
- toJSON
- toObject
- typeKey
- validateBeforeSave
- versionKey
- optimisticConcurrency
- collation
- timeseries
- selectPopulatedPaths
- skipVersioning
- timestamps
- storeSubdocValidationError
- collectionOptions
- methods
- query
- autoSearchIndex
- readConcern
オプション: autoIndex
デフォルトでは、Mongooseのinit()
関数は、MongoDBへの接続に成功した後、Model.createIndexes()
を呼び出すことによって、モデルのスキーマで定義されたすべてのインデックスを作成します。インデックスを自動的に作成することは、開発環境とテスト環境には最適です。しかし、インデックスの構築は、本番データベースに大きな負荷をかける可能性もあります。本番環境でインデックスを慎重に管理する場合は、autoIndex
をfalseに設定できます。
const schema = new Schema({ /* ... */ }, { autoIndex: false });
const Clock = mongoose.model('Clock', schema);
Clock.ensureIndexes(callback);
autoIndex
オプションは、デフォルトでtrue
に設定されています。mongoose.set('autoIndex', false);
を設定することで、このデフォルトを変更できます。
オプション: autoCreate
Mongooseがインデックスを作成する前に、デフォルトではModel.createCollection()
を呼び出して、MongoDBで基礎となるコレクションを作成します。createCollection()
を呼び出すと、コレクションのデフォルトの照合順序が照合順序オプションに基づいて設定され、capped
スキーマオプションを設定した場合、コレクションはキャップされたコレクションとして確立されます。
autoCreate
を false
に設定することで、この動作を無効にできます。 mongoose.set('autoCreate', false)
を使用してください。autoIndex
と同様に、autoCreate
は開発およびテスト環境に役立ちますが、不要なデータベース呼び出しを避けるために、本番環境では無効にすることをお勧めします。
残念ながら、createCollection()
は既存のコレクションを変更できません。たとえば、スキーマに capped: { size: 1024 }
を追加し、既存のコレクションがキャップされていない場合、createCollection()
は既存のコレクションを**上書きしません**。これは、MongoDB サーバーがコレクションを最初に削除せずにコレクションのオプションを変更することを許可していないためです。
const schema = new Schema({ name: String }, {
autoCreate: false,
capped: { size: 1024 }
});
const Test = mongoose.model('Test', schema);
// No-op if collection already exists, even if the collection is not capped.
// This means that `capped` won't be applied if the 'tests' collection already exists.
await Test.createCollection();
オプション: bufferCommands
デフォルトでは、Mongoose は接続が切断されると、ドライバーが再接続できるまでコマンドをバッファリングします。バッファリングを無効にするには、bufferCommands
を false に設定します。
const schema = new Schema({ /* ... */ }, { bufferCommands: false });
スキーマの bufferCommands
オプションは、グローバルな bufferCommands
オプションをオーバーライドします。
mongoose.set('bufferCommands', true);
// Schema option below overrides the above, if the schema option is set.
const schema = new Schema({ /* ... */ }, { bufferCommands: false });
オプション: bufferTimeoutMS
bufferCommands
が有効になっている場合、このオプションは、Mongoose のバッファリングがエラーをスローするまで待機する最大時間を設定します。指定しない場合、Mongoose は 10000(10 秒)を使用します。
// If an operation is buffered for more than 1 second, throw an error.
const schema = new Schema({ /* ... */ }, { bufferTimeoutMS: 1000 });
オプション: capped
Mongoose は MongoDB の キャップされたコレクション をサポートしています。基になる MongoDB コレクションを capped
に指定するには、capped
オプションをコレクションの最大サイズ(バイト単位)に設定します。
new Schema({ /* ... */ }, { capped: 1024 });
max
のような追加のオプションを渡す必要がある場合は、capped
オプションをオブジェクトに設定することもできます。こちらを参照してください。この場合、必須である size
オプションを明示的に渡す必要があります。
new Schema({ /* ... */ }, { capped: { size: 1024, max: 1000, autoIndexId: true } });
オプション: collection
Mongoose はデフォルトで、モデル名を utils.toCollectionName
メソッドに渡すことでコレクション名を作成します。このメソッドは名前を複数形にします。コレクションに別の名前が必要な場合は、このオプションを設定してください。
const dataSchema = new Schema({ /* ... */ }, { collection: 'data' });
オプション: discriminatorKey
ディスクリミネーター を定義すると、Mongoose はドキュメントがどのディスクリミネーターのインスタンスであるかを格納するパスをスキーマに追加します。デフォルトでは、Mongoose は __t
パスを追加しますが、discriminatorKey
を設定してこのデフォルトを上書きできます。
const baseSchema = new Schema({}, { discriminatorKey: 'type' });
const BaseModel = mongoose.model('Test', baseSchema);
const personSchema = new Schema({ name: String });
const PersonModel = BaseModel.discriminator('Person', personSchema);
const doc = new PersonModel({ name: 'James T. Kirk' });
// Without `discriminatorKey`, Mongoose would store the discriminator
// key in `__t` instead of `type`
doc.type; // 'Person'
オプション: excludeIndexes
excludeIndexes
が true
の場合、Mongoose は指定されたサブドキュメントスキーマからインデックスを作成しません。このオプションは、スキーマがサブドキュメントパスまたはドキュメント配列パスで使用されている場合にのみ機能します。モデルのトップレベルスキーマに設定されている場合、Mongoose はこのオプションを無視します。デフォルトは false
です。
const childSchema1 = Schema({
name: { type: String, index: true }
});
const childSchema2 = Schema({
name: { type: String, index: true }
}, { excludeIndexes: true });
// Mongoose will create an index on `child1.name`, but **not** `child2.name`, because `excludeIndexes`
// is true on `childSchema2`
const User = new Schema({
name: { type: String, index: true },
child1: childSchema1,
child2: childSchema2
});
オプション: id
Mongoose は、各スキーマに id
仮想ゲッターをデフォルトで割り当てます。これは、ドキュメントの _id
フィールドを文字列にキャストした値を返します。ObjectId の場合は、その hexString を返します。スキーマに id
ゲッターを追加したくない場合は、スキーマの構築時にこのオプションを渡して無効にすることができます。
// default behavior
const schema = new Schema({ name: String });
const Page = mongoose.model('Page', schema);
const p = new Page({ name: 'mongodb.org' });
console.log(p.id); // '50341373e894ad16347efe01'
// disabled id
const schema = new Schema({ name: String }, { id: false });
const Page = mongoose.model('Page', schema);
const p = new Page({ name: 'mongodb.org' });
console.log(p.id); // undefined
オプション: _id
Mongoose は、Schema コンストラクターに渡されていない場合、各スキーマに _id
フィールドをデフォルトで割り当てます。割り当てられる型は、MongoDB のデフォルトの動作に合わせて ObjectId です。スキーマに _id
をまったく追加したくない場合は、このオプションを使用して無効にすることができます。
このオプションは、**サブドキュメントでのみ**使用できます。Mongoose は ID を知らずにドキュメントを保存できないため、_id
がないドキュメントを保存しようとするとエラーが発生します。
// default behavior
const schema = new Schema({ name: String });
const Page = mongoose.model('Page', schema);
const p = new Page({ name: 'mongodb.org' });
console.log(p); // { _id: '50341373e894ad16347efe01', name: 'mongodb.org' }
// disabled _id
const childSchema = new Schema({ name: String }, { _id: false });
const parentSchema = new Schema({ children: [childSchema] });
const Model = mongoose.model('Model', parentSchema);
Model.create({ children: [{ name: 'Luke' }] }, (error, doc) => {
// doc.children[0]._id will be undefined
});
オプション: minimize
Mongoose は、デフォルトで、空のオブジェクトを削除することにより、スキーマを「最小化」します。
const schema = new Schema({ name: String, inventory: {} });
const Character = mongoose.model('Character', schema);
// will store `inventory` field if it is not empty
const frodo = new Character({ name: 'Frodo', inventory: { ringOfPower: 1 } });
await frodo.save();
let doc = await Character.findOne({ name: 'Frodo' }).lean();
doc.inventory; // { ringOfPower: 1 }
// will not store `inventory` field if it is empty
const sam = new Character({ name: 'Sam', inventory: {} });
await sam.save();
doc = await Character.findOne({ name: 'Sam' }).lean();
doc.inventory; // undefined
この動作は、minimize
オプションを false
に設定することでオーバーライドできます。そうすると、空のオブジェクトが保存されます。
const schema = new Schema({ name: String, inventory: {} }, { minimize: false });
const Character = mongoose.model('Character', schema);
// will store `inventory` if empty
const sam = new Character({ name: 'Sam', inventory: {} });
await sam.save();
doc = await Character.findOne({ name: 'Sam' }).lean();
doc.inventory; // {}
オブジェクトが空かどうかを確認するには、$isEmpty()
ヘルパーを使用できます。
const sam = new Character({ name: 'Sam', inventory: {} });
sam.$isEmpty('inventory'); // true
sam.inventory.barrowBlade = 1;
sam.$isEmpty('inventory'); // false
オプション: read
スキーマレベルで query#read オプションを設定できます。これにより、モデルから派生したすべてのクエリにデフォルトの ReadPreferences を適用する方法が提供されます。
const schema = new Schema({ /* ... */ }, { read: 'primary' }); // also aliased as 'p'
const schema = new Schema({ /* ... */ }, { read: 'primaryPreferred' }); // aliased as 'pp'
const schema = new Schema({ /* ... */ }, { read: 'secondary' }); // aliased as 's'
const schema = new Schema({ /* ... */ }, { read: 'secondaryPreferred' }); // aliased as 'sp'
const schema = new Schema({ /* ... */ }, { read: 'nearest' }); // aliased as 'n'
各 pref のエイリアスも許可されているため、'secondaryPreferred' を入力してスペルミスをする代わりに、'sp' を渡すだけで済みます。
read オプションを使用すると、タグセットを指定することもできます。これにより、ドライバー に、レプリカセットのどのメンバーから読み取りを試みるべきかを指示できます。タグセットの詳細については、こちらとこちらを参照してください。
注: 接続時にドライバーの読み取り優先順位戦略オプションを指定することもできます。
// pings the replset members periodically to track network latency
const options = { replset: { strategy: 'ping' } };
mongoose.connect(uri, options);
const schema = new Schema({ /* ... */ }, { read: ['nearest', { disk: 'ssd' }] });
mongoose.model('JellyBean', schema);
オプション: writeConcern
スキーマレベルで 書き込みに関する懸念事項 を設定できます。
const schema = new Schema({ name: String }, {
writeConcern: {
w: 'majority',
j: true,
wtimeout: 1000
}
});
オプション: shardKey
shardKey
オプションは、シャード化された MongoDB アーキテクチャ を使用する場合に使用されます。各シャード化されたコレクションには、すべての挿入/更新操作に存在する必要があるシャードキーが与えられます。このスキーマオプションを同じシャードキーに設定するだけで済みます。
new Schema({ /* ... */ }, { shardKey: { tag: 1, name: 1 } });
Mongoose は shardcollection
コマンドを自動的に送信しません。シャードは自分で構成する必要があります。
オプション: strict
strict オプション(デフォルトで有効)は、スキーマで指定されていない値がモデルコンストラクターに渡された場合、それらの値がデータベースに保存されないようにします。
const thingSchema = new Schema({ /* ... */ })
const Thing = mongoose.model('Thing', thingSchema);
const thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // iAmNotInTheSchema is not saved to the db
// set to false..
const thingSchema = new Schema({ /* ... */ }, { strict: false });
const thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // iAmNotInTheSchema is now saved to the db!!
これは、doc.set()
を使用してプロパティ値を設定する場合にも影響します。
const thingSchema = new Schema({ /* ... */ });
const Thing = mongoose.model('Thing', thingSchema);
const thing = new Thing;
thing.set('iAmNotInTheSchema', true);
thing.save(); // iAmNotInTheSchema is not saved to the db
この値は、2 番目のブール引数を渡すことで、モデルインスタンスレベルでオーバーライドできます。
const Thing = mongoose.model('Thing');
const thing = new Thing(doc, true); // enables strict mode
const thing = new Thing(doc, false); // disables strict mode
strict
オプションを "throw"
に設定することもできます。そうすると、不良データが削除される代わりにエラーが発生します。
注: スキーマに存在しないキー/値がインスタンスに設定されている場合、スキーマオプションに関係なく、常に無視されます。
const thingSchema = new Schema({ /* ... */ });
const Thing = mongoose.model('Thing', thingSchema);
const thing = new Thing;
thing.iAmNotInTheSchema = true;
thing.save(); // iAmNotInTheSchema is never saved to the db
オプション: strictQuery
Mongoose は、クエリフィルターに対して厳格モードを回避するための個別の strictQuery
オプションをサポートしています。これは、空のクエリフィルターによって Mongoose がモデル内のすべてのドキュメントを返すことが原因で問題が発生する可能性があるためです。
const mySchema = new Schema({ field: Number }, { strict: true });
const MyModel = mongoose.model('Test', mySchema);
// Mongoose will filter out `notInSchema: 1` because `strict: true`, meaning this query will return
// _all_ documents in the 'tests' collection
MyModel.find({ notInSchema: 1 });
strict
オプションは更新に適用されます。strictQuery
オプションは、クエリフィルターのみに適用されます。
// Mongoose will strip out `notInSchema` from the update if `strict` is
// not `false`
MyModel.updateMany({}, { $set: { notInSchema: 1 } });
Mongoose には、クエリへの filter
パラメーターに対して厳格モードを切り替えるための個別の strictQuery
オプションがあります。
const mySchema = new Schema({ field: Number }, {
strict: true,
strictQuery: false // Turn off strict mode for query filters
});
const MyModel = mongoose.model('Test', mySchema);
// Mongoose will not strip out `notInSchema: 1` because `strictQuery` is false
MyModel.find({ notInSchema: 1 });
一般的に、ユーザー定義のオブジェクトをクエリフィルターとして渡すことは**お勧めしません**。
// Don't do this!
const docs = await MyModel.find(req.query);
// Do this instead:
const docs = await MyModel.find({ name: req.query.name, age: req.query.age }).setOptions({ sanitizeFilter: true });
Mongoose 7 では、strictQuery
はデフォルトで false
です。ただし、この動作はグローバルにオーバーライドできます。
// Set `strictQuery` to `true` to omit unknown fields in queries.
mongoose.set('strictQuery', true);
オプション: toJSON
toObject オプションとまったく同じですが、ドキュメントの toJSON
メソッド が呼び出された場合にのみ適用されます。
const schema = new Schema({ name: String });
schema.path('name').get(function(v) {
return v + ' is my name';
});
schema.set('toJSON', { getters: true, virtuals: false });
const M = mongoose.model('Person', schema);
const m = new M({ name: 'Max Headroom' });
console.log(m.toObject()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom' }
console.log(m.toJSON()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }
// since we know toJSON is called whenever a js object is stringified:
console.log(JSON.stringify(m)); // { "_id": "504e0cd7dd992d9be2f20b6f", "name": "Max Headroom is my name" }
使用可能なすべての toJSON/toObject
オプションを表示するには、こちらを参照してください。
オプション: toObject
ドキュメントには、Mongoose ドキュメントをプレーンな JavaScript オブジェクトに変換する toObject メソッドがあります。このメソッドはいくつかのオプションを受け付けます。これらのオプションをドキュメントごとに適用する代わりに、スキーマレベルでオプションを宣言し、デフォルトでスキーマのすべてのドキュメントに適用することができます。
すべての仮想プロパティを console.log
出力に表示するには、toObject
オプションを { getters: true }
に設定します。
const schema = new Schema({ name: String });
schema.path('name').get(function(v) {
return v + ' is my name';
});
schema.set('toObject', { getters: true });
const M = mongoose.model('Person', schema);
const m = new M({ name: 'Max Headroom' });
console.log(m); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }
使用可能なすべての toObject
オプションを表示するには、こちらを参照してください。
オプション: typeKey
デフォルトでは、スキーマにキー 'type' を持つオブジェクトがある場合、Mongoose はそれを型宣言として解釈します。
// Mongoose interprets this as 'loc is a String'
const schema = new Schema({ loc: { type: String, coordinates: [Number] } });
geoJSON のようなアプリケーションでは、'type' プロパティが重要です。Mongoose が型宣言を見つけるために使用するキーを制御する場合は、'typeKey' スキーマオプションを設定します。
const schema = new Schema({
// Mongoose interprets this as 'loc is an object with 2 keys, type and coordinates'
loc: { type: String, coordinates: [Number] },
// Mongoose interprets this as 'name is a String'
name: { $type: String }
}, { typeKey: '$type' }); // A '$type' key means this object is a type declaration
オプション: validateBeforeSave
デフォルトでは、ドキュメントはデータベースに保存される前に自動的に検証されます。これは、無効なドキュメントの保存を防ぐためです。検証を手動で処理し、検証に合格しないオブジェクトを保存できるようにする場合は、validateBeforeSave
を false に設定できます。
const schema = new Schema({ name: String });
schema.set('validateBeforeSave', false);
schema.path('name').validate(function(value) {
return value != null;
});
const M = mongoose.model('Person', schema);
const m = new M({ name: null });
m.validate(function(err) {
console.log(err); // Will tell you that null is not allowed.
});
m.save(); // Succeeds despite being invalid
オプション: versionKey
versionKey
は、Mongoose によって最初に作成されたときに各ドキュメントに設定されるプロパティです。このキーの値には、ドキュメントの内部 リビジョン が含まれています。versionKey
オプションは、バージョン管理に使用するパスを表す文字列です。デフォルトは __v
です。これがアプリケーションと競合する場合は、次のように構成できます。
const schema = new Schema({ name: 'string' });
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'mongoose v3' });
await thing.save(); // { __v: 0, name: 'mongoose v3' }
// customized versionKey
new Schema({ /* ... */ }, { versionKey: '_somethingElse' })
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'mongoose v3' });
thing.save(); // { _somethingElse: 0, name: 'mongoose v3' }
Mongoose のデフォルトのバージョン管理は、完全な 楽観的同時実行制御 ソリューションではありません。Mongoose のデフォルトのバージョン管理は、下記のように配列でのみ動作します。
// 2 copies of the same document
const doc1 = await Model.findOne({ _id });
const doc2 = await Model.findOne({ _id });
// Delete first 3 comments from `doc1`
doc1.comments.splice(0, 3);
await doc1.save();
// The below `save()` will throw a VersionError, because you're trying to
// modify the comment at index 1, and the above `splice()` removed that
// comment.
doc2.set('comments.1.body', 'new comment');
await doc2.save();
save()
に対する楽観的同時実行制御のサポートが必要な場合は、optimisticConcurrency
オプション を設定できます。
versionKey
を false
に設定することで、ドキュメントのバージョン管理を無効にすることもできます。何が起こるかを理解していない限り、バージョン管理を無効にしないでください。
new Schema({ /* ... */ }, { versionKey: false });
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'no versioning please' });
thing.save(); // { name: 'no versioning please' }
Mongoose は save()
を使用した場合にのみバージョンキーを更新します。update()
、findOneAndUpdate()
などを使用する場合、Mongoose はバージョンキーを**更新しません**。回避策として、以下のミドルウェアを使用できます。
schema.pre('findOneAndUpdate', function() {
const update = this.getUpdate();
if (update.__v != null) {
delete update.__v;
}
const keys = ['$set', '$setOnInsert'];
for (const key of keys) {
if (update[key] != null && update[key].__v != null) {
delete update[key].__v;
if (Object.keys(update[key]).length === 0) {
delete update[key];
}
}
}
update.$inc = update.$inc || {};
update.$inc.__v = 1;
});
オプション: optimisticConcurrency
楽観的同時実行制御 は、find()
または findOne()
を使用してロードしたときと、save()
を使用して更新したときの間に、更新しているドキュメントが変更されていないことを確認するための戦略です。
たとえば、photos
のリストと、この住宅が検索に表示されるかどうかを表す status
を含む House
モデルがあるとします。ステータスが 'APPROVED'
の住宅には、少なくとも 2 つの photos
が必要です。住宅ドキュメントを承認するロジックは、次のように実装できます。
async function markApproved(id) {
const house = await House.findOne({ _id });
if (house.photos.length < 2) {
throw new Error('House must have at least two photos!');
}
house.status = 'APPROVED';
await house.save();
}
markApproved()
関数は単独では正しく見えますが、潜在的な問題がある可能性があります。findOne()
呼び出しと save()
呼び出しの間に、別の関数が住宅の写真を削除した場合、どうなるでしょうか?たとえば、次のコードは成功します。
const house = await House.findOne({ _id });
if (house.photos.length < 2) {
throw new Error('House must have at least two photos!');
}
const house2 = await House.findOne({ _id });
house2.photos = [];
await house2.save();
// Marks the house as 'APPROVED' even though it has 0 photos!
house.status = 'APPROVED';
await house.save();
House
モデルのスキーマでoptimisticConcurrency
オプションを設定した場合、上記のスクリプトはエラーをスローします。
const House = mongoose.model('House', Schema({
status: String,
photos: [String]
}, { optimisticConcurrency: true }));
const house = await House.findOne({ _id });
if (house.photos.length < 2) {
throw new Error('House must have at least two photos!');
}
const house2 = await House.findOne({ _id });
house2.photos = [];
await house2.save();
// Throws 'VersionError: No matching document found for id "..." version 0'
house.status = 'APPROVED';
await house.save();
オプション: collation
すべてのクエリと集約に対するデフォルトの照合順序を設定します。照合順序の概要はこちら(初心者向け)。
const schema = new Schema({
name: String
}, { collation: { locale: 'en_US', strength: 1 } });
const MyModel = db.model('MyModel', schema);
MyModel.create([{ name: 'val' }, { name: 'Val' }]).
then(() => {
return MyModel.find({ name: 'val' });
}).
then((docs) => {
// `docs` will contain both docs, because `strength: 1` means
// MongoDB will ignore case when matching.
});
オプション: timeseries
スキーマでtimeseries
オプションを設定すると、Mongooseはそのスキーマから作成するすべてのモデルに対して時系列コレクションを作成します。
const schema = Schema({ name: String, timestamp: Date, metadata: Object }, {
timeseries: {
timeField: 'timestamp',
metaField: 'metadata',
granularity: 'hours'
},
autoCreate: false,
expireAfterSeconds: 86400
});
// `Test` collection will be a timeseries collection
const Test = db.model('Test', schema);
オプション: skipVersioning
skipVersioning
を使用すると、バージョン管理からパスを除外できます(つまり、これらのパスが更新されても、内部リビジョンはインクリメントされません)。自分のやっていることが完全に理解していない限り、これを実行しないでください。サブドキュメントの場合、完全修飾パスを使用して親ドキュメントにこれを含めます。
new Schema({ /* ... */ }, { skipVersioning: { dontVersionMe: true } });
thing.dontVersionMe.push('hey');
thing.save(); // version is not incremented
オプション: timestamps
timestamps
オプションは、MongooseにスキーマにcreatedAt
フィールドとupdatedAt
フィールドを割り当てるように指示します。割り当てられる型はDateです。
デフォルトでは、フィールド名はcreatedAt
とupdatedAt
です。timestamps.createdAt
とtimestamps.updatedAt
を設定して、フィールド名をカスタマイズできます。
timestamps
が内部で動作する方法は次のとおりです。
- 新しいドキュメントを作成する場合、Mongooseは
createdAt
とupdatedAt
を作成時刻に設定します。 - ドキュメントを更新する場合、Mongooseは
updatedAt
を$set
オブジェクトに追加します。 - 更新操作で
upsert: true
を設定した場合、Mongooseは$setOnInsert
演算子を使用して、upsert
操作によって新しいドキュメントが挿入された場合にcreatedAt
をドキュメントに追加します。
const thingSchema = new Schema({ /* ... */ }, { timestamps: { createdAt: 'created_at' } });
const Thing = mongoose.model('Thing', thingSchema);
const thing = new Thing();
await thing.save(); // `created_at` & `updatedAt` will be included
// With updates, Mongoose will add `updatedAt` to `$set`
await Thing.updateOne({}, { $set: { name: 'Test' } });
// If you set upsert: true, Mongoose will add `created_at` to `$setOnInsert` as well
await Thing.findOneAndUpdate({}, { $set: { name: 'Test2' } });
// Mongoose also adds timestamps to bulkWrite() operations
// See https://mongoose.dokyumento.jp/docs/api/model.html#model_Model-bulkWrite
await Thing.bulkWrite([
{
insertOne: {
document: {
name: 'Jean-Luc Picard',
ship: 'USS Stargazer'
// Mongoose will add `created_at` and `updatedAt`
}
}
},
{
updateOne: {
filter: { name: 'Jean-Luc Picard' },
update: {
$set: {
ship: 'USS Enterprise'
// Mongoose will add `updatedAt`
}
}
}
}
]);
デフォルトでは、Mongooseは現在時刻を取得するためにnew Date()
を使用します。現在時刻を取得するためにMongooseが使用する関数を上書きする場合は、timestamps.currentTime
オプションを設定できます。Mongooseは、現在時刻を取得する必要があるたびにtimestamps.currentTime
関数を呼び出します。
const schema = Schema({
createdAt: Number,
updatedAt: Number,
name: String
}, {
// Make Mongoose use Unix time (seconds since Jan 1, 1970)
timestamps: { currentTime: () => Math.floor(Date.now() / 1000) }
});
オプション: pluginTags
Mongooseは、すべてのスキーマに適用されるグローバルプラグインを定義することをサポートしています。
// Add a `meta` property to all schemas
mongoose.plugin(function myPlugin(schema) {
schema.add({ meta: {} });
});
特定のプラグインを一部のスキーマのみに適用する場合があります。その場合は、スキーマにpluginTags
を追加できます。
const schema1 = new Schema({
name: String
}, { pluginTags: ['useMetaPlugin'] });
const schema2 = new Schema({
name: String
});
tags
オプションを使用してplugin()
を呼び出すと、MongooseはそのプラグインをpluginTags
に一致するエントリを持つスキーマのみに適用します。
// Add a `meta` property to all schemas
mongoose.plugin(function myPlugin(schema) {
schema.add({ meta: {} });
}, { tags: ['useMetaPlugin'] });
オプション: selectPopulatedPaths
デフォルトでは、Mongooseは明示的に除外しない限り、自動的にすべての関連付けられたパスをselect()
します。
const bookSchema = new Schema({
title: 'String',
author: { type: 'ObjectId', ref: 'Person' }
});
const Book = mongoose.model('Book', bookSchema);
// By default, Mongoose will add `author` to the below `select()`.
await Book.find().select('title').populate('author');
// In other words, the below query is equivalent to the above
await Book.find().select('title author').populate('author');
デフォルトで関連付けられたフィールドの選択をオプトアウトするには、スキーマでselectPopulatedPaths
をfalse
に設定します。
const bookSchema = new Schema({
title: 'String',
author: { type: 'ObjectId', ref: 'Person' }
}, { selectPopulatedPaths: false });
const Book = mongoose.model('Book', bookSchema);
// Because `selectPopulatedPaths` is false, the below doc will **not**
// contain an `author` property.
const doc = await Book.findOne().select('title').populate('author');
オプション: storeSubdocValidationError
レガシーの理由により、単一のネストされたスキーマのサブパスで検証エラーが発生した場合、Mongooseはその単一のネストされたスキーマパスにも検証エラーがあったことを記録します。例:
const childSchema = new Schema({ name: { type: String, required: true } });
const parentSchema = new Schema({ child: childSchema });
const Parent = mongoose.model('Parent', parentSchema);
// Will contain an error for both 'child.name' _and_ 'child'
new Parent({ child: {} }).validateSync().errors;
Mongooseが親エラーのみを報告するようにするには、子スキーマでstoreSubdocValidationError
をfalse
に設定します。
const childSchema = new Schema({
name: { type: String, required: true }
}, { storeSubdocValidationError: false }); // <-- set on the child schema
const parentSchema = new Schema({ child: childSchema });
const Parent = mongoose.model('Parent', parentSchema);
// Will only contain an error for 'child.name'
new Parent({ child: {} }).validateSync().errors;
オプション: collectionOptions
collation
やcapped
などのオプションは、新しいコレクションを作成する際にMongooseがMongoDBに渡すオプションに影響を与えます。MongooseスキーマはほとんどのMongoDB createCollection()
オプションをサポートしますが、すべてではありません。collectionOptions
オプションを使用して任意のcreateCollection()
オプションを設定できます。Mongooseは、スキーマに対してcreateCollection()
を呼び出す際に、collectionOptions
をデフォルト値として使用します。
const schema = new Schema({ name: String }, {
autoCreate: false,
collectionOptions: {
capped: true,
max: 1000
}
});
const Test = mongoose.model('Test', schema);
// Equivalent to `createCollection({ capped: true, max: 1000 })`
await Test.createCollection();
オプション: autoSearchIndex
autoIndex
に似ていますが、スキーマで定義されているAtlas検索インデックスを自動的に作成します。autoIndex
とは異なり、このオプションはデフォルトでfalseです。
const schema = new Schema({ name: String }, { autoSearchIndex: true });
schema.searchIndex({
name: 'my-index',
definition: { mappings: { dynamic: true } }
});
// Will automatically attempt to create the `my-index` search index.
const Test = mongoose.model('Test', schema);
オプション: readConcern
リードコンサーンはwriteConcern
に似ていますが、find()
やfindOne()
などの読み取り操作用です。デフォルトのreadConcern
を設定するには、次のようにreadConcern
オプションをスキーマコンストラクタに渡します。
const eventSchema = new mongoose.Schema(
{ name: String },
{
readConcern: { level: 'available' } // <-- set default readConcern for all queries
}
);
ES6クラスを使用
スキーマには、ES6クラスからMongooseスキーマを作成するために使用できるloadClass()
メソッドがあります。
- ES6クラスメソッドはMongooseメソッドになります。
- ES6クラススタティックメソッドはMongooseスタティックメソッドになります。
- ES6ゲッターとセッターはMongooseバーチャルになります。
ES6クラスからスキーマを作成するためにloadClass()
を使用する例を次に示します。
class MyClass {
myMethod() { return 42; }
static myStatic() { return 42; }
get myVirtual() { return 42; }
}
const schema = new mongoose.Schema();
schema.loadClass(MyClass);
console.log(schema.methods); // { myMethod: [Function: myMethod] }
console.log(schema.statics); // { myStatic: [Function: myStatic] }
console.log(schema.virtuals); // { myVirtual: VirtualType { ... } }
プラグイン可能
スキーマはまたプラグイン可能であり、再利用可能な機能をプラグインにパッケージ化して、コミュニティと共有したり、プロジェクト間で共有したりできます。
参考資料
Mongooseスキーマの代替紹介はこちら。
MongoDBを最大限に活用するには、MongoDBスキーマ設計の基本を学ぶ必要があります。SQLスキーマ設計(第3正規形)はストレージコストを最小限に抑えるために設計されましたが、MongoDBスキーマ設計は一般的なクエリをできるだけ高速にすることを目的としています。MongoDBスキーマ設計の6つの経験則に関するブログシリーズは、クエリを高速化するための基本ルールを学ぶための優れたリソースです。
Node.jsでMongoDBスキーマ設計を習得したいユーザーは、MongoDB Node.jsドライバのオリジナル作者であるChristian KvalheimによるThe Little MongoDB Schema Design Bookを参照してください。この本では、eコマース、ウィキ、予約など、さまざまなユースケースに対してパフォーマンスの高いスキーマを実装する方法を示しています。
次へ
Schemas
について説明したので、次はSchemaTypesを見てみましょう。