7.x から 8.x への移行

Mongoose 7.x から Mongoose 8.x へ移行する際には、いくつかの後方互換性のない変更点に注意する必要があります。

Mongoose 6.x 以前のバージョンを使用している場合は、Mongoose 6.x から 7.x への移行ガイド を読んで、Mongoose 8 にアップグレードする前に Mongoose 7.x にアップグレードしてください。

Mongoose 8 にアップグレードする前に、MongoDB Node.js ドライバーの v6.0.0 リリースノート を確認することをお勧めします。

findOneAndUpdate()rawResult オプションを削除

findOneAndUpdate()findOneAndReplace()、および findOneAndDelete()rawResult オプションは、includeResultMetadata オプションに置き換えられました。

const filter = { name: 'Will Riker' };
const update = { age: 29 };

const res = await Character.findOneAndUpdate(filter, update, {
  new: true,
  upsert: true,
  // Replace `rawResult: true` with `includeResultMetadata: true`
  includeResultMetadata: true
});

Mongoose 8 の includeResultMetadata は、rawResult と同じように動作します。

Document.prototype.deleteOne はクエリを返すようになりました

Mongoose 7 では、doc.deleteOne()doc を解決する Promise を返していました。Mongoose 8 では、doc.deleteOne() は、より簡単なチェーン化と doc.updateOne() との整合性のために、クエリを返します。

const numberOne = await Character.findOne({ name: 'Will Riker' });

// In Mongoose 7, q is a Promise that resolves to `numberOne`
// In Mongoose 8, q is a Query.
const q = numberOne.deleteOne();

// In Mongoose 7, `res === numberOne`
// In Mongoose 8, `res` is a `DeleteResult`.
const res = await q;

MongoDB Node Driver 6

Mongoose 8 は MongoDB Node ドライバーの v6.x を使用しています。MongoDB Node ドライバー v6 には、Mongoose に影響を与えるいくつかの重要な変更点があります。

  1. ObjectId コンストラクターは、長さ 12 の文字列を受け付けなくなりました。Mongoose 7 では、new mongoose.Types.ObjectId('12charstring') は完全に有効でした。Mongoose 8 では、new mongoose.Types.ObjectId('12charstring') はエラーをスローします。

  2. 非推奨の SSL オプションが削除されました

    • sslCA -> tlsCAFile
    • sslCRL -> tlsCRLFile
    • sslCert -> tlsCertificateKeyFile
    • sslKey -> tlsCertificateKeyFile
    • sslPass -> tlsCertificateKeyFilePassword
    • sslValidate -> tlsAllowInvalidCertificates
    • tlsCertificateFile -> tlsCertificateKeyFile

findOneAndRemove() を削除

Mongoose 7 では、findOneAndRemove() は後方互換性のために Mongoose がサポートしていた findOneAndDelete() のエイリアスでした。Mongoose 8 は findOneAndRemove() をサポートしなくなりました。代わりに findOneAndDelete() を使用してください。

count() を削除

Model.count()Query.prototype.count() は Mongoose 8 で削除されました。代わりに Model.countDocuments()Query.prototype.countDocuments() を使用してください。

id セッターを削除

Mongoose 7.4 では、Mongoose は doc.id = '0'.repeat(24)doc._id = '0'.repeat(24) と同等にする id セッターを導入しました。Mongoose 8 では、そのセッターは削除されました。

必須ではない文字列列挙型には null が有効

Mongoose 8 より前では、文字列パスで enumnull に設定すると、そのパスが required でなくてもバリデーションエラーが発生していました。Mongoose 8 では、required が設定されていない場合、enum を使用していても、文字列パスを null に設定できます。

const schema = new Schema({
  status: {
    type: String,
    enum: ['on', 'off']
  }
});
const Test = mongoose.model('Test', schema);

// Works fine in Mongoose 8
// Throws a `ValidationError` in Mongoose 7
await Test.create({ status: null });

既存のドキュメントを更新する際に save() で最小化を適用

Mongoose 7 では、Mongoose は新しいドキュメントを保存する場合のみ最小化を適用し、既存のドキュメントを更新する場合は適用しませんでしたが、Mongoose 8 では既存のドキュメント更新時にも最小化が適用されます。

const schema = new Schema({
  nested: {
    field1: Number
  }
});
const Test = mongoose.model('Test', schema);

// Both Mongoose 7 and Mongoose 8 strip out empty objects when saving
// a new document in MongoDB by default
const { _id } = await Test.create({ nested: {} });
let rawDoc = await Test.findById(_id).lean();
rawDoc.nested; // undefined

// Mongoose 8 will also strip out empty objects when saving an
// existing document in MongoDB
const doc = await Test.findById(_id);
doc.nested = {};
doc.markModified('nested');
await doc.save();

let rawDoc = await Test.findById(_id).lean();
rawDoc.nested; // undefined in Mongoose 8, {} in Mongoose 7

ディスクリミネーターパスより前に基本スキーマパスを適用

これは、Mongoose 8 では、ディスクリミネーターパスのゲッターとセッターは、基本パスのゲッターとセッターの *後* に実行されることを意味します。Mongoose 7 では、ディスクリミネーターパスのゲッターとセッターは、基本パスのゲッターとセッターの *前* に実行されていました。


const schema = new Schema({
  name: {
    type: String,
    get(v) {
      console.log('Base schema getter');
      return v;
    }
  }
});

const Test = mongoose.model('Test', schema);
const D = Test.discriminator('D', new Schema({
  otherProp: {
    type: String,
    get(v) {
      console.log('Discriminator schema getter');
      return v;
    }
  }
}));

const doc = new D({ name: 'test', otherProp: 'test' });
// In Mongoose 8, prints "Base schema getter" followed by "Discriminator schema getter"
// In Mongoose 7, prints "Discriminator schema getter" followed by "Base schema getter"
console.log(doc.toObject({ getters: true }));

findOneAndUpdate()overwrite オプションを削除

Mongoose 7 およびそれ以前のバージョンでは、findOneAndUpdate()updateOne()、および update()overwrite オプションがサポートされていました。Mongoose 7 より前では、overwriteupdate パラメーターを $set でラップすることをスキップするため、findOneAndUpdate()update() は一致したドキュメントを上書きしていました。Mongoose 7 では、overwrite を設定すると、後方互換性を維持するために、findOneAndUpdate()findOneAndReplace() に、updateOne()replaceOne() に変換されていました。

Mongoose 8 では、overwrite オプションはサポートされなくなりました。ドキュメント全体を上書きする場合は、findOneAndReplace() または replaceOne() を使用してください。

orFail() と upsert を使用した findOneAndUpdate() の動作を変更

Mongoose 7 では、findOneAndUpdate(filter, update, { upsert: true }).orFail() は、新しいドキュメントが upsert されると DocumentNotFoundError をスローしていました。つまり、findOneAndUpdate().orFail() は、新しいドキュメントが upsert された場合でも、ドキュメントが見つからない場合は常にエラーをスローしていました。

Mongoose 8 では、findOneAndUpdate(filter, update, { upsert: true }).orFail() は常に成功します。findOneAndUpdate().orFail() は、ドキュメントが見つからない場合ではなく、ドキュメントが返されない場合に DocumentNotFoundError をスローするようになりました。

作成は、すべての保存が完了するまでエラーをスローしません

Mongoose 7 では、create() は、デフォルトで save() がエラーをスローするとすぐにエラーをスローしていました。Mongoose 8 は、代わりにすべての save() 呼び出しが終了するまで待ってから、発生した最初のエラーをスローします。そのため、create() は Mongoose 7 と Mongoose 8 の両方で同じエラーをスローしますが、Mongoose 8 はエラーをスローするまでに時間がかかる場合があります。

const schema = new Schema({
  name: {
    type: String,
    enum: ['Badger', 'Mushroom']
  }
});
schema.pre('save', async function() {
  await new Promise(resolve => setTimeout(resolve, 1000));
});
const Test = mongoose.model('Test', schema);

const err = await Test.create([
  { name: 'Badger' },
  { name: 'Mushroom' },
  { name: 'Cow' }
]).then(() => null, err => err);
err; // ValidationError

// In Mongoose 7, there would be 0 documents, because `Test.create()`
// would throw before 'Badger' and 'Mushroom' are inserted
// In Mongoose 8, there will be 2 documents. `Test.create()` waits until
// 'Badger' and 'Mushroom' are inserted before throwing.
await Test.countDocuments();

Model.validate() はオブジェクトのコピーを返します

Mongoose 7 では、Model.validate() は渡されたオブジェクトを変更する可能性がありました。Mongoose 8 は代わりに、最初に渡されたオブジェクトをコピーします。

const schema = new Schema({ answer: Number });
const Test = mongoose.model('Test', schema);

const obj = { answer: '42' };
const res = Test.validate(obj);

typeof obj.answer; // 'string' in Mongoose 8, 'number' in Mongoose 7 
typeof res.answer; // 'number' in both Mongoose 7 and Mongoose 8

TypeScript でオプションフィールドに null を許可

Mongoose 8 では、TypeScript で自動的に推論されたスキーマ型は、オプションフィールドに null を許可します。Mongoose 7 では、オプションフィールドは null ではなく undefined のみを許可していました。

const schema = new Schema({ name: String });
const TestModel = model('Test', schema);

const doc = new TestModel();

// In Mongoose 8, this type is `string | null | undefined`.
// In Mongoose 7, this type is `string | undefined`
doc.name;

TypeScript ではモデルコンストラクターのプロパティはすべてオプションです

Mongoose 8 では、デフォルトでモデルコンストラクターにプロパティは必要ありません。

import {Schema, model, Model} from 'mongoose';

interface IDocument {
  name: string;
  createdAt: Date;
  updatedAt: Date;
}

const documentSchema = new Schema<IDocument>(
  { name: { type: String, required: true } },
  { timestamps: true }
);

const TestModel = model<IDocument>('Document', documentSchema);

// Would throw a compile error in Mongoose 7, compiles in Mongoose 8
const newDoc = new TestModel({
  name: 'Foo'
});

// Explicitly pass generic param to constructor to specify the expected
// type of the model constructor param. The following will cause TS
// to complain about missing `createdAt` and `updatedAt` in Mongoose 8.
const newDoc2 = new TestModel<IDocument>({
  name: 'Foo'
});

スキーマから distinct() の戻り値の型を推論

interface User {
  name: string;
  email: string;
  avatar?: string;
}
const schema = new Schema<User>({
  name: { type: String, required: true },
  email: { type: String, required: true },
  avatar: String
});

// Works in Mongoose 8. Compile error in Mongoose 7.
const names: string[] = await MyModel.distinct('name');