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

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

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

strictQuery

strictQueryはデフォルトでfalseになりました。

const mySchema = new Schema({ field: Number });
const MyModel = mongoose.model('Test', mySchema);

// Mongoose will not strip out `notInSchema: 1` because `strictQuery` is false by default
const docs = await MyModel.find({ notInSchema: 1 });
// Empty array in Mongoose 7. In Mongoose 6, this would contain all documents in MyModel
docs;

remove()の削除

ドキュメントとモデルのremove()メソッドは削除されました。代わりにdeleteOne()またはdeleteMany()を使用してください。

const mySchema = new Schema({ field: Number });
const MyModel = mongoose.model('Test', mySchema);

// Change this:
await MyModel.remove(filter);

// To this:
await MyModel.deleteOne(filter);
// Or this, if you want to delete multiple:
await MyModel.deleteMany(filter);

// For documents, change this:
await doc.remove();

// To this:
await doc.deleteOne();

deleteOne()フックはデフォルトでクエリミドルウェアとして扱われることに注意してください。そのため、ミドルウェアの場合は、以下のようにしてください。

// Replace this:
schema.pre('remove', function() {
  /* ... */
});

// With this:
schema.pre('deleteOne', { document: true, query: false }, function() {
  /* ... */
});

コールバックのサポート終了

以下の関数は、コールバックを受け付けなくなりました。常にPromiseを返します。

  • Aggregate.prototype.exec
  • Aggregate.prototype.explain
  • AggregationCursor.prototype.close
  • AggregationCursor.prototype.next
  • AggregationCursor.prototype.eachAsync
  • Connection.prototype.startSession
  • Connection.prototype.dropCollection
  • Connection.prototype.createCollection
  • Connection.prototype.dropDatabase
  • Connection.prototype.openUri
  • Connection.prototype.close
  • Connection.prototype.destroy
  • Document.prototype.populate
  • Document.prototype.validate
  • Mongoose.prototype.connect
  • Mongoose.prototype.createConnection
  • Model.prototype.save
  • Model.aggregate
  • Model.bulkWrite
  • Model.cleanIndexes
  • Model.countDocuments
  • Model.create
  • Model.createCollection
  • Model.createIndexes
  • Model.deleteOne
  • Model.deleteMany
  • Model.distinct
  • Model.ensureIndexes
  • Model.estimatedDocumentCount
  • Model.exists
  • Model.find
  • Model.findById
  • Model.findByIdAndUpdate
  • Model.findByIdAndReplace
  • Model.findOne
  • Model.findOneAndDelete
  • Model.findOneAndUpdate
  • Model.findOneAndRemove
  • Model.insertMany
  • Model.listIndexes
  • Model.replaceOne
  • Model.syncIndexes
  • Model.updateMany
  • Model.updateOne
  • Query.prototype.find
  • Query.prototype.findOne
  • Query.prototype.findOneAndDelete
  • Query.prototype.findOneAndUpdate
  • Query.prototype.findOneAndRemove
  • Query.prototype.findOneAndReplace
  • Query.prototype.validate
  • Query.prototype.deleteOne
  • Query.prototype.deleteMany
  • Query.prototype.exec
  • QueryCursor.prototype.close
  • QueryCursor.prototype.next
  • QueryCursor.prototype.eachAsync

上記の関数をコールバックで使用している場合は、async/awaitに切り替えることをお勧めします。async関数が使用できない場合は、Promiseを使用してください。レガシーコードベースのリファクタリングに支援が必要な場合は、ChatGPTを使用したMastering JSのコールバックからasync awaitへの変換ツールをご覧ください。

// Before
conn.startSession(function(err, session) {
  // ...
});

// After
const session = await conn.startSession();
// Or:
conn.startSession().then(sesson => { /* ... */ });

// With error handling
try {
  await conn.startSession();
} catch (err) { /* ... */ }
// Or:
const [err, session] = await conn.startSession().then(
  session => ([null, session]),
  err => ([err, null])
);

update()の削除

Model.update()Query.prototype.update()、およびDocument.prototype.update()は削除されました。代わりにupdateOne()を使用してください。

// Before
await Model.update(filter, update);
await doc.update(update);

// After
await Model.updateOne(filter, update);
await doc.updateOne(update);

ObjectIdはnewキーワードが必要

Mongoose 6以前では、newキーワードを使用せずに新しいObjectIdを定義できました。

// Works in Mongoose 6
// Throws "Class constructor ObjectId cannot be invoked without 'new'" in Mongoose 7
const oid = mongoose.Types.ObjectId('0'.repeat(24));

Mongoose 7では、ObjectIdJavaScriptクラスになったため、newキーワードを使用する必要があります。

// Works in Mongoose 6 and Mongoose 7
const oid = new mongoose.Types.ObjectId('0'.repeat(24));

idセッター

Mongoose 7.4以降、Mongooseの組み込みのid仮想プロパティ(ドキュメントの_idを文字列として格納する)にセッターが追加され、idを介してドキュメントの_idプロパティを変更できるようになりました。

const doc = await TestModel.findOne();

doc.id = '000000000000000000000000';
doc._id; // ObjectId('000000000000000000000000')

これは、objid_idの両方が含まれているnew TestModel(obj)を作成する場合、またはdoc.set()を使用する場合に、予期しない動作を引き起こす可能性があります。

// Because `id` is after `_id`, the `id` will overwrite the `_id`
const doc = new TestModel({
  _id: '000000000000000000000000',
  id: '111111111111111111111111'
});

doc._id; // ObjectId('111111111111111111111111')

互換性の問題のため、idセッターはMongoose 8で後に削除されました

ディスクリミネータスキーマはデフォルトでベーススキーマのオプションを使用

Model.discriminator()を使用すると、Mongooseはデフォルトでディスクリミネータベーススキーマのオプションを使用するようになります。つまり、子スキーマのオプションをベーススキーマのオプションと一致するように明示的に設定する必要はありません。

const baseSchema = Schema({}, { typeKey: '$type' });
const Base = db.model('Base', baseSchema);

// In Mongoose 6.x, the `Base.discriminator()` call would throw because
// no `typeKey` option. In Mongoose 7, Mongoose uses the base schema's
// `typeKey` by default.
const childSchema = new Schema({}, {});
const Test = Base.discriminator('Child', childSchema);

Test.schema.options.typeKey; // '$type'

castForQueryWrapperの削除、castForQuery()のシグネチャ更新

Mongooseは、SchemaTypeのcastForQuery()メソッドを常に3つの引数($conditionalvalue、およびcontext)で呼び出すようになりました。独自のcastForQuery()メソッドを定義するカスタムスキーマタイプを実装している場合は、メソッドを次のように更新する必要があります。

// Mongoose 6.x format:
MySchemaType.prototype.castForQuery = function($conditional, value) {
  if (arguments.length === 2) {
    // Handle casting value with `$conditional` - $eq, $in, $not, etc.
  } else {
    value = $conditional;
    // Handle casting `value` with no conditional
  }
};

// Mongoose 7.x format
MySchemaType.prototype.castForQuery = function($conditional, value, context) {
  if ($conditional != null) {
    // Handle casting value with `$conditional` - $eq, $in, $not, etc.
  } else {
    // Handle casting `value` with no conditional
  }
};

Schema.prototype.add()でスキーマオプションをコピー

Mongooseは、あるスキーマを別のスキーマに追加するときに、ユーザー定義のスキーマオプションをコピーするようになりました。たとえば、以下の`childSchema`は、`baseSchema`の`id`オプションと`toJSON`オプションを取得します。

const baseSchema = new Schema({ created: Date }, { id: true, toJSON: { virtuals: true } });
const childSchema = new Schema([baseSchema, { name: String }]);

childSchema.options.toJSON; // { virtuals: true } in Mongoose 7. undefined in Mongoose 6.

これは、スキーマの配列を使用して新しいスキーマを作成する場合と、次のようにadd()を呼び出す場合の両方に適用されます。

childSchema.add(new Schema({}, { toObject: { virtuals: true } }));

childSchema.options.toObject; // { virtuals: true } in Mongoose 7. undefined in Mongoose 6.

ObjectIdのbsontypeは小文字のdに変更

ObjectIdの内部プロパティ_bsontypeは、Mongoose 6の`'ObjectID'`とは異なり、Mongoose 7では`'ObjectId'`になります。

const oid = new mongoose.Types.ObjectId();

oid._bsontype; // 'ObjectId' in Mongoose 7, 'ObjectID' in older versions of Mongoose

_bsontypeを使用してオブジェクトがObjectIdかどうかを確認している場合は、更新してください。これは、Mongooseを使用するライブラリにも影響を与える可能性があります。

mapReduceの削除

MongoDBはmapReduceをサポートしなくなったため、Mongoose 7にはModel.mapReduce()関数がなくなりました。 mapReduce()の代わりに集計フレームワークを使用してください。

// The following no longer works in Mongoose 7.
const o = {
  map: function() {
    emit(this.author, 1);
  },
  reduce: function(k, vals) {
    return vals.length;
  }
};

await MR.mapReduce(o);

カスタムPromiseライブラリのサポート終了

Mongoose 7は、カスタムPromiseライブラリのプラグインをサポートしなくなりました。そのため、以下のようにしても、Mongoose 7でBluebird Promiseが返されることはなくなりました。

const mongoose = require('mongoose');

// No-op on Mongoose 7
mongoose.Promise = require('bluebird');

すべてのPromiseにグローバルにBluebirdを使用する場合は、次のようにします。

global.Promise = require('bluebird');

TypeScript特有の変更点

LeanDocumentextends Documentのサポート終了

Mongoose 7は、LeanDocument型をエクスポートしなくなり、extends Documentするドキュメント型をModel<>に渡すことをサポートしなくなりました。

// No longer supported
interface ITest extends Document {
  name?: string;
}
const Test = model<ITest>('Test', schema);

// Do this instead, no `extends Document`
interface ITest {
  name?: string;
}
const Test = model<ITest>('Test', schema);

// If you need to access the hydrated document type, use the following code
type TestDocument = ReturnType<(typeof Test)['hydrate']>;

HydratedDocumentの新しいパラメータ

MongooseのHydratedDocument型は、生のドキュメントインターフェースを、仮想、メソッドなどを含む、ハイドレートされたMongooseドキュメントの型に変換します。Mongoose 7では、HydratedDocumentへのジェネリックパラメータが変更されました。Mongoose 6では、ジェネリックパラメータは次のとおりでした。

type HydratedDocument<
  DocType,
  TMethodsAndOverrides = {},
  TVirtuals = {}
> = Document<unknown, any, DocType> &
Require_id<DocType> &
TMethodsAndOverrides &
TVirtuals;

Mongoose 7では、新しい型は次のとおりです。

type HydratedDocument<
  DocType,
  TOverrides = {},
  TQueryHelpers = {}
> = Document<unknown, TQueryHelpers, DocType> &
Require_id<DocType> &
TOverrides;

Mongoose 7では、最初のパラメータは生のドキュメントインターフェース、2番目のパラメータはドキュメント固有のオーバーライド(通常は仮想とメソッド)、3番目のパラメータはドキュメントのモデルに関連付けられたクエリヘルパーです。

主な違いは、Mongoose 6では3番目のジェネリックパラメータがドキュメントの_仮想_であったのに対し、Mongoose 7では3番目のジェネリックパラメータがドキュメントの_クエリヘルパー_であることです。

// Mongoose 6 version:
type UserDocument = HydratedDocument<TUser, TUserMethods, TUserVirtuals>;

// Mongoose 7:
type UserDocument = HydratedDocument<TUser, TUserMethods & TUserVirtuals, TUserQueryHelpers>;