5.xから6.xへの移行

注意: Mongoose 5のサポートは2024年3月1日に終了する予定です。詳細については、バージョンサポートガイドをご覧ください。

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

Mongoose 4.xをまだ使用している場合は、Mongoose 4.xから5.xへの移行ガイドを読み、まずMongoose 5.xにアップグレードしてください。

バージョン要件

MongooseはNode.js >= 12.0.0を必要とするようになりました。Mongooseは、MongoDBサーバーのバージョン3.0.0までをサポートしています。

MongoDB Driver 4.0

Mongooseは、MongoDB Node driverのv4.xを使用するようになりました。詳細については、MongoDB Node driversの移行ガイドを参照してください。以下は、最も注目すべき変更点の一部です。

  • MongoDB Driver 4.xはTypeScriptで記述されており、独自のTypeScript型定義があります。これらは@types/mongodbと競合する可能性があるため、TypeScriptコンパイラエラーが発生した場合は、最新バージョンの@types/mongodb(空のスタブ)にアップグレードしてください。
  • 接続のpoolSizeオプションは、minPoolSizemaxPoolSizeに置き換えられました。Mongoose 5.xのpoolSizeオプションは、Mongoose 6のmaxPoolSizeオプションと同等です。maxPoolSizeのデフォルト値は100に増加しました。
  • updateOne()およびupdateMany()の結果が異なるようになりました。
  • deleteOne()およびdeleteMany()の結果には、nプロパティがなくなりました。
const res = await TestModel.updateMany({}, { someProperty: 'someValue' });

res.matchedCount; // Number of documents that were found that match the filter. Replaces `res.n`
res.modifiedCount; // Number of documents modified. Replaces `res.nModified`
res.upsertedCount; // Number of documents upserted. Replaces `res.upserted`
const res = await TestModel.deleteMany({});

// In Mongoose 6: `{ acknowledged: true, deletedCount: 2 }`
// In Mongoose 5: `{ n: 2, ok: 1, deletedCount: 2 }`
res;

res.deletedCount; // Number of documents that were deleted. Replaces `res.n`

非推奨警告オプションの廃止

useNewUrlParseruseUnifiedTopologyuseFindAndModify、およびuseCreateIndexはサポートされなくなったオプションです。Mongoose 6は常にuseNewUrlParseruseUnifiedTopology、およびuseCreateIndextrueuseFindAndModifyfalseであるかのように動作します。これらのオプションをコードから削除してください。

// No longer necessary:
mongoose.set('useFindAndModify', false);

await mongoose.connect('mongodb://127.0.0.1:27017/test', {
  useNewUrlParser: true, // <-- no longer necessary
  useUnifiedTopology: true // <-- no longer necessary
});

接続のasPromise()メソッド

Mongoose接続はもはやthenableではありません。つまり、await mongoose.createConnection(uri)Mongooseが接続するのを待たなくなりました。代わりにmongoose.createConnection(uri).asPromise()を使用してください。 #8810を参照してください。

// The below no longer works in Mongoose 6
await mongoose.createConnection(uri);

// Do this instead
await mongoose.createConnection(uri).asPromise();

mongoose.connect()はPromiseを返す

mongoose.connect()関数は、Mongooseインスタンスではなく、常にpromiseを返すようになりました。

クエリの重複実行

Mongooseは、同じクエリオブジェクトを2回実行することを許可しなくなりました。実行すると、Query was already executedエラーが表示されます。同じクエリインスタンスを2回実行することは、通常、コールバックとpromiseを混在させていることを示していますが、同じクエリを2回実行する必要がある場合は、Query#clone()を呼び出してクエリを複製し、再実行できます。 gh-7398を参照してください。

// Results in 'Query was already executed' error, because technically this `find()` query executes twice.
await Model.find({}, function(err, result) {});

const q = Model.find();
await q;
await q.clone(); // Can `clone()` the query to allow executing the query again

Model.exists(...)は、booleanではなくリーンなドキュメントを返すようになりました

// in Mongoose 5.x, `existingUser` used to be a boolean
// now `existingUser` will be either `{ _id: ObjectId(...) }` or `null`.
const existingUser = await User.exists({ name: 'John' });
if (existingUser) {
  console.log(existingUser._id);
}

strictQueryはデフォルトでstrictと同等になった

Mongooseは、strictQueryオプションをサポートしなくなりました。strictを使用する必要があります。Mongoose 6.0.10では、strictQueryオプションが復活しました。ただし、strictQueryはデフォルトでstrictに結び付けられています。つまり、デフォルトでは、Mongooseはスキーマにないクエリフィルタープロパティをフィルタリングします。

const userSchema = new Schema({ name: String });
const User = mongoose.model('User', userSchema);

// By default, this is equivalent to `User.find()` because Mongoose filters out `notInSchema`
await User.find({ notInSchema: 1 });

// Set `strictQuery: false` to opt in to filtering by properties that aren't in the schema
await User.find({ notInSchema: 1 }, null, { strictQuery: false });
// equivalent:
await User.find({ notInSchema: 1 }).setOptions({ strictQuery: false });

グローバルにstrictQueryを無効にして上書きすることもできます

mongoose.set('strictQuery', false);

MongoErrorはMongoServerErrorになった

MongoDB Node.js Driver v4.xでは、「MongoError」は「MongoServerError」になりました。ハードコードされた文字列「MongoError」に依存するコードを変更してください。

デフォルトで判別子スキーマをクローンする

Mongooseは、デフォルトで判別子スキーマを複製するようになりました。つまり、再帰的な埋め込み判別子を使用している場合は、discriminator(){ clone: false }を渡す必要があります。

// In Mongoose 6, these two are equivalent:
User.discriminator('author', authorSchema);
User.discriminator('author', authorSchema.clone());

// To opt out if `clone()` is causing issues, pass `clone: false`
User.discriminator('author', authorSchema, { clone: false });

isValidObjectId()の簡略化とisObjectIdOrHexString()の分離

Mongoose 5では、mongoose.isValidObjectId()は数値などの値に対してfalseを返していましたが、これはMongoDBドライバーのObjectId.isValid()関数と矛盾していました。技術的には、任意のJavaScript数をMongoDB ObjectIdに変換できます。

Mongoose 6では、mongoose.isValidObjectId()は、一貫性のためにmongoose.Types.ObjectId.isValid()のラッパーにすぎません。

Mongoose 6.2.5には、isValidObjectId()のより一般的なユースケースをより適切に捉えるmongoose.isObjectIdOrHexString()関数が含まれるようになりました。指定された値は、ObjectIdインスタンスまたはObjectIdを表す24文字の16進文字列ですか?

// `isValidObjectId()` returns `true` for some surprising values, because these
// values are _technically_ ObjectId representations
mongoose.isValidObjectId(new mongoose.Types.ObjectId()); // true
mongoose.isValidObjectId('0123456789ab'); // true
mongoose.isValidObjectId(6); // true
mongoose.isValidObjectId(new User({ name: 'test' })); // true

// `isObjectIdOrHexString()` instead only returns `true` for ObjectIds and 24
// character hex strings.
mongoose.isObjectIdOrHexString(new mongoose.Types.ObjectId()); // true
mongoose.isObjectIdOrHexString('62261a65d66c6be0a63c051f'); // true
mongoose.isObjectIdOrHexString('0123456789ab'); // false
mongoose.isObjectIdOrHexString(6); // false

スキーマで定義されたドキュメントキーの順序

Mongooseは、ユーザー定義のオブジェクトではなく、スキーマで指定されたキーの順序でキーを持つオブジェクトを保存するようになりました。したがって、Object.keys(new User({ name: String, email: String }).toObject()['name', 'email']['email', 'name']かは、スキーマでnameemailが定義されている順序によって異なります。

const schema = new Schema({
  profile: {
    name: {
      first: String,
      last: String
    }
  }
});
const Test = db.model('Test', schema);

const doc = new Test({
  profile: { name: { last: 'Musashi', first: 'Miyamoto' } }
});

// Note that 'first' comes before 'last', even though the argument to `new Test()` flips the key order.
// Mongoose uses the schema's key order, not the provided objects' key order.
assert.deepEqual(Object.keys(doc.toObject().profile.name), ['first', 'last']);

sanitizeFiltertrusted()

Mongoose 6には、クエリセレクターインジェクション攻撃から保護するための、グローバルおよびクエリへの新しいsanitizeFilterオプションが導入されています。sanitizeFilterを有効にすると、Mongooseはクエリフィルター内の任意のオブジェクトを$eqでラップします。

// Mongoose will convert this filter into `{ username: 'val', pwd: { $eq: { $ne: null } } }`, preventing
// a query selector injection.
await Test.find({ username: 'val', pwd: { $ne: null } }).setOptions({ sanitizeFilter: true });

クエリセレクターを明示的に許可するには、mongoose.trusted()を使用します

// `mongoose.trusted()` allows query selectors through
await Test.find({ username: 'val', pwd: mongoose.trusted({ $ne: null }) }).setOptions({ sanitizeFilter: true });

omitUndefinedの削除: Mongooseは、undefinedキーをnullに設定するのではなく、更新時に削除するようになった

Mongoose 5.xでは、更新操作でキーをundefinedに設定することは、nullに設定することと同じでした。

let res = await Test.findOneAndUpdate({}, { $set: { name: undefined } }, { new: true });

res.name; // `null` in Mongoose 5.x

// Equivalent to `findOneAndUpdate({}, {}, { new: true })` because `omitUndefined` will
// remove `name: undefined`
res = await Test.findOneAndUpdate({}, { $set: { name: undefined } }, { new: true, omitUndefined: true });

Mongoose 5.xは、undefinedキーを削除するためのomitUndefinedオプションをサポートしていました。Mongoose 6.xでは、omitUndefinedオプションは削除され、Mongooseは常にundefinedキーを削除するようになりました。

// In Mongoose 6, equivalent to `findOneAndUpdate({}, {}, { new: true })` because Mongoose will
// remove `name: undefined`
const res = await Test.findOneAndUpdate({}, { $set: { name: undefined } }, { new: true });

唯一の回避策は、更新でプロパティを明示的にnullに設定することです

const res = await Test.findOneAndUpdate({}, { $set: { name: null } }, { new: true });

デフォルト関数へのドキュメントパラメータ

Mongooseは、アロー関数をデフォルトで使用するのに役立つ、ドキュメントを最初のパラメータとしてdefault関数に渡すようになりました。

これは、default: mongoose.Types.ObjectIdのように、異なるパラメータを期待する関数をdefaultに渡す場合に影響を与える可能性があります。gh-9633を参照してください。ドキュメントを使用**しない**デフォルト関数を渡している場合は、誤って動作を変更する可能性のあるパラメータを渡さないように、default: myFunctiondefault: () => myFunction()に変更します。

const schema = new Schema({
  name: String,
  age: Number,
  canVote: {
    type: Boolean,
    // Default functions now receive a `doc` parameter, helpful for arrow functions
    default: doc => doc.age >= 18
  }
});

配列はプロキシ

Mongoose配列はES6プロキシになりました。配列インデックスを直接設定した後、markModified()する必要はなくなりました。

const post = await BlogPost.findOne();

post.tags[0] = 'javascript';
await post.save(); // Works, no need for `markModified()`!

typePojoToMixed

type: { name: String }で宣言されたスキーマパスは、Mongoose 5のMixedではなく、Mongoose 6では単一のネストされたサブドキュメントになります。これにより、typePojoToMixedオプションが不要になります。 gh-7181を参照してください。

// In Mongoose 6, the below makes `foo` into a subdocument with a `name` property.
// In Mongoose 5, the below would make `foo` a `Mixed` type, _unless_ you set `typePojoToMixed: false`.
const schema = new Schema({
  foo: { type: { name: String } }
});

strictPopulate()

スキーマで定義されていないパスをpopulate()すると、Mongooseはエラーをスローするようになりました。これは、Query#populate()を使用する場合など、ローカルスキーマを推論できる場合にのみ当てはまります。POJOでModel.populate()を呼び出す場合は当てはまりません。 gh-5124を参照してください。

サブドキュメントref関数のコンテキスト

関数refまたはrefPathを使用してサブドキュメントをポピュレートする場合、thisはトップレベルのドキュメントではなく、ポピュレートされているサブドキュメントになりました。 #8469を参照してください。

const schema = new Schema({
  works: [{
    modelId: String,
    data: {
      type: mongoose.ObjectId,
      ref: function(doc) {
        // In Mongoose 6, `doc` is the array element, so you can access `modelId`.
        // In Mongoose 5, `doc` was the top-level document.
        return doc.modelId;
      }
    }
  }]
});

スキーマの予約名に関する警告

saveisNew、およびその他のMongoose予約名をスキーマパス名として使用すると、エラーではなく警告がトリガーされるようになりました。スキーマオプションでsuppressReservedKeysWarningを設定することにより、警告を抑制できます。例:new Schema({ save: String }, { suppressReservedKeysWarning: true })。これは、これらの予約名に依存するプラグインを壊す可能性があることに注意してください。

サブドキュメントパス

単一のネストされたサブドキュメントは、「サブドキュメントパス」に名前が変更されました。したがって、SchemaSingleNestedOptionsSchemaSubdocumentOptionsになり、mongoose.Schema.Types.Embeddedmongoose.Schema.Types.Subdocumentになります。 gh-10419を参照してください

アグリゲーションカーソルの作成

Aggregate#cursor()は、Query#cursor()との一貫性を保つために、AggregationCursorインスタンスを返すようになりました。アグリゲーションカーソルを取得するために、Model.aggregate(pipeline).cursor().exec()を実行する必要はなくなり、Model.aggregate(pipeline).cursor()だけで実行できます。

autoCreateはデフォルトでtrue

readPreferenceがsecondaryまたはsecondaryPreferredでない限り、autoCreateはデフォルトでtrueです。これは、Mongooseがインデックスを作成する前にすべてのモデルの基礎となるコレクションを作成しようとすることを意味します。readPreferenceがsecondaryまたはsecondaryPreferredの場合、MongooseはautoCreateautoIndexの両方をデフォルトでfalseにします。これは、createCollection()createIndex()の両方がセカンダリに接続されたときに失敗するためです。

context: 'query'の廃止

クエリのcontextオプションは削除されました。現在、Mongooseは常にcontext = 'query'を使用します。

ポピュレートされたパスを持つカスタムバリデーター

Mongoose 6は、常に(ドキュメント自体ではなく、idで)デポピュレートされたパスでバリデーターを呼び出します。Mongoose 5では、パスがポピュレートされている場合、Mongooseはポピュレートされたドキュメントでバリデーターを呼び出していました。 #8042を参照してください

レプリカセットでの切断イベント

レプリカセットに接続されている場合、プライマリへの接続が失われると、接続は「disconnected」を発生させるようになりました。Mongoose 5では、接続はレプリカセットのすべてのメンバーへの接続が失われた場合にのみ「disconnected」を発生させていました。

ただし、Mongoose 6は、接続が切断されている間、コマンドをバッファリング**しません**。したがって、Mongoose接続が切断された状態にあっても、readPreference = 'secondary'でクエリなどのコマンドを正常に実行できます。

execPopulate()の削除

Document#populate()はpromiseを返すようになり、チェーンできなくなりました。

  • await doc.populate('path1').populate('path2').execPopulate();await doc.populate(['path1', 'path2']);に置き換えてください

  • await doc.populate('path1', 'select1').populate('path2', 'select2').execPopulate();を以下に置き換えてください

    await doc.populate([{path: 'path1', select: 'select1'}, {path: 'path2', select: 'select2'}]);

空の配列でのcreate()

v6.0のawait Model.create([])は、空の配列が指定されると空の配列を返しますが、v5.0ではundefinedを返していました。コードのいずれかで、出力がundefinedかどうかを確認している場合は、await Model.create(...)が配列が指定されると常に配列を返すという前提で変更する必要があります。

ネストされたパスのマージの削除

doc.set({ child: { age: 21 } }) の動作が、child がネストされたパスであろうとサブドキュメントであろうと、同じになりました。Mongoose は child の値を上書きします。Mongoose 5 では、child がネストされたパスの場合、この操作は child をマージしていました。

ObjectId valueOf()

Mongoose は ObjectId に valueOf() 関数を追加しました。これにより、ObjectId を文字列と == で比較できるようになりました。

const a = ObjectId('6143b55ac9a762738b15d4f0');

a == '6143b55ac9a762738b15d4f0'; // true

イミュータブルなcreatedAt

timestamps: true を設定した場合、Mongoose は createdAt プロパティを immutable にします。詳細は gh-10139 を参照してください。

バリデーターisAsyncの削除

isAsyncvalidate のオプションではなくなりました。代わりに async function を使用してください。

safeの削除

safe はスキーマ、クエリ、save() のオプションではなくなりました。代わりに writeConcern を使用してください。

SchemaType の set パラメータ

Mongoose は、セッター関数を呼び出す際に、Mongoose 5 の schemaType ではなく、2番目のパラメータとして priorValue を渡すようになりました。

const userSchema = new Schema({
  name: {
    type: String,
    trimStart: true,
    set: trimStartSetter
  }
});

// in v5.x the parameters were (value, schemaType), in v6.x the parameters are (value, priorValue, schemaType).
function trimStartSetter(val, priorValue, schemaType) {
  if (schemaType.options.trimStart && typeof val === 'string') {
    return val.trimStart();
  }
  return val;
}

const User = mongoose.model('User', userSchema);

const user = new User({ name: 'Robert Martin' });
console.log(user.name); // 'robert martin'

toObject()toJSON()はネストされたスキーマのminimizeを使用する

この変更は技術的には 5.10.5 でリリースされましたが、5.9.x から 6.x に移行するユーザーに問題を引き起こしました。Mongoose < 5.10.5 では、toObject()toJSON() はデフォルトでトップレベルのスキーマの minimize オプションを使用していました。

const child = new Schema({ thing: Schema.Types.Mixed });
const parent = new Schema({ child }, { minimize: false });
const Parent = model('Parent', parent);
const p = new Parent({ child: { thing: {} } });

// In v5.10.4, would contain `child.thing` because `toObject()` uses `parent` schema's `minimize` option
// In `>= 5.10.5`, `child.thing` is omitted because `child` schema has `minimize: true`
console.log(p.toObject());

回避策として、minimize を明示的に toObject() または toJSON() に渡すことができます。

console.log(p.toObject({ minimize: false }));

または、親の minimize オプションを継承するために、child スキーマをインラインで定義する (Mongoose 6 のみ) こともできます。

const parent = new Schema({
  // Implicitly creates a new schema with the top-level schema's `minimize` option.
  child: { type: { thing: Schema.Types.Mixed } }
}, { minimize: false });

Query.prototype.populate()のデフォルトモデルの廃止

Mongoose 5 では、ref がない混合型または他のパスで populate() を呼び出すと、クエリのモデルを使用するようにフォールバックしていました。

const testSchema = new mongoose.Schema({
  data: String,
  parents: Array // Array of mixed
});

const Test = mongoose.model('Test', testSchema);

// The below `populate()`...
await Test.findOne().populate('parents');
// Is a shorthand for the following populate in Mongoose 5
await Test.findOne().populate({ path: 'parents', model: Test });

Mongoose 6 では、refrefPath、または model がないパスを populate すると、何も行われません。

// The below `populate()` does nothing.
await Test.findOne().populate('parents');

MongoDB Driverの新しいURLパーサーは、一部のnpmパッケージと互換性がない

Mongoose 6 が使用する MongoDB Node ドライバーのバージョンは、他の npm パッケージとの互換性問題がいくつか知られている URLパーサーモジュール に依存しています。これにより、互換性のないパッケージのいずれかを使用すると、Invalid URL: mongodb+srv://username:password@development.xyz.mongodb.net/abc のようなエラーが発生する可能性があります。非互換パッケージの一覧はこちらで確認できます

TypeScriptの変更

Schema クラスは、4つではなく3つのジェネリックパラメータを受け取るようになりました。3番目のジェネリックパラメータである SchemaDefinitionType は、最初のジェネリックパラメータである DocType と同じになりました。new Schema<UserDocument, UserModel, User>(schemaDefinition)new Schema<UserDocument, UserModel>(schemaDefinition) に置き換えてください。

Types.ObjectId はクラスになったため、new mongoose.Types.ObjectId() を使用して新しい ObjectId を作成するときに new を省略できなくなりました。現在、JavaScript では new を省略できますが、TypeScript では 必ず new を記述する必要があります。

以下のレガシー型は削除されました。

  • ModelUpdateOptions
  • DocumentQuery
  • HookSyncCallback
  • HookAsyncCallback
  • HookErrorCallback
  • HookNextFunction
  • HookDoneFunction
  • SchemaTypeOpts
  • ConnectionOptions

Mongoose 6 は、仮想ゲッターとセッターの this にドキュメントの型を推論します。Mongoose 5.x では、以下のコードで thisany でした。

schema.virtual('myVirtual').get(function() {
  this; // any in Mongoose 5.x
});

Mongoose 6 では、this はドキュメントの型に設定されます。

const schema = new Schema({ name: String });

schema.virtual('myVirtual').get(function() {
  this.name; // string
});

reconnectTriesおよびreconnectIntervalオプションの削除

reconnectTries および reconnectInterval オプションは、もはや必要ないため削除されました。

MongoDB ノードドライバーは、MongoDB が長期間ダウンしている場合でも、serverSelectionTimeoutMS まで常に操作を再試行します。そのため、再試行がなくなることや MongoDB に再接続しようとすることはありません。