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

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

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

まだ Mongoose 3.x を使用している場合は、Mongoose 3.x から 4.x への移行ガイドをお読みください。

バージョン要件

Mongoose は Node.js >= 4.0.0 および MongoDB >= 3.0.0 を必要とするようになりました。MongoDB 2.6 および Node.js < 4 はどちらも 2016 年に EOL になりました。

クエリミドルウェア

クエリミドルウェアは、mongoose.model() または db.model() を呼び出したときにコンパイルされるようになりました。mongoose.model() を呼び出した後にクエリミドルウェアを追加した場合、そのミドルウェアは呼び出されません

const schema = new Schema({ name: String });
const MyModel = mongoose.model('Test', schema);
schema.pre('find', () => { console.log('find!'); });

MyModel.find().exec(function() {
  // In mongoose 4.x, the above `.find()` will print "find!"
  // In mongoose 5.x, "find!" will **not** be printed.
  // Call `pre('find')` **before** calling `mongoose.model()` to make the middleware apply.
});

mongoose.connect() の Promise とコールバック

mongoose.connect() および mongoose.disconnect() は、コールバックが指定されていない場合は Promise を返し、それ以外の場合は null を返すようになりました。Mongoose シングルトンは返しません

// Worked in mongoose 4. Does **not** work in mongoose 5, `mongoose.connect()`
// now returns a promise consistently. This is to avoid the horrible things
// we've done to allow mongoose to be a thenable that resolves to itself.
mongoose.connect('mongodb://127.0.0.1:27017/test').model('Test', new Schema({}));

// Do this instead
mongoose.connect('mongodb://127.0.0.1:27017/test');
mongoose.model('Test', new Schema({}));

接続ロジックと useMongoClient

useMongoClient オプションは Mongoose 5 で削除され、常に true になりました。その結果、Mongoose 5 は、useMongoClient オプションがオフの場合に Mongoose 4.x で動作していた mongoose.connect() のいくつかの関数シグネチャをサポートしなくなりました。以下は、Mongoose 5.x で動作しない mongoose.connect() の呼び出しの例です。

  • mongoose.connect('127.0.0.1', 27017);
  • mongoose.connect('127.0.0.1', 'mydb', 27017);
  • mongoose.connect('mongodb://host1:27017,mongodb://host2:27017');

Mongoose 5.x では、mongoose.connect() および mongoose.createConnection() の最初のパラメータ(指定されている場合)は、MongoDB 接続文字列である必要があります。接続文字列とオプションは、MongoDB Node.js ドライバーの MongoClient.connect() 関数に渡されます。Mongoose は接続文字列を変更しませんが、mongoose.connect() および mongoose.createConnection() は、MongoDB ドライバーがサポートするオプションに加えて、いくつかの追加オプションをサポートしています。

セッターの順序

4.x では、セッターは逆順に実行されます

const schema = new Schema({ name: String });
schema.path('name').
  set(() => console.log('This will print 2nd')).
  set(() => console.log('This will print first'));

5.x では、セッターは宣言された順序で実行されます。

const schema = new Schema({ name: String });
schema.path('name').
  set(() => console.log('This will print first')).
  set(() => console.log('This will print 2nd'));

パスがポピュレートされているかどうかの確認

Mongoose 5.1.0 では、パスがポピュレートされているかどうかに関係なく ObjectId を取得できる _id ゲッターが ObjectIds に導入されました。

const blogPostSchema = new Schema({
  title: String,
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Author'
  }
});
const BlogPost = mongoose.model('BlogPost', blogPostSchema);

await BlogPost.create({ title: 'test', author: author._id });
const blogPost = await BlogPost.findOne();

console.log(blogPost.author); // '5b207f84e8061d1d2711b421'
// New in Mongoose 5.1.0: this will print '5b207f84e8061d1d2711b421' as well
console.log(blogPost.author._id);

await blogPost.populate('author');
console.log(blogPost.author._id); // '5b207f84e8061d1d2711b421'

その結果、blogPost.author._idポピュレートされているかどうかを確認する方法として有効ではなくなりました。代わりに、blogPost.populated('author') != null または blogPost.author instanceof mongoose.Types.ObjectId を使用して、author がポピュレートされているかどうかを確認してください。

mongoose.set('objectIdGetter', false) を呼び出して、この動作を変更できることに注意してください。

remove() および deleteX() の戻り値

deleteOne()deleteMany()、および remove() は、完全なドライバーの WriteOpResult オブジェクトではなく、結果オブジェクトに解決されるようになりました。

// In 4.x, this is how you got the number of documents deleted
MyModel.deleteMany().then(res => console.log(res.result.n));
// In 5.x this is how you get the number of documents deleted
MyModel.deleteMany().then(res => res.n);

集計カーソル

4.x の useMongooseAggCursor オプションは、常にオンになりました。これは、mongoose 5 での集計カーソルの新しい構文です

// When you call `.cursor()`, `.exec()` will now return a mongoose aggregation
// cursor.
const cursor = MyModel.aggregate([{ $match: { name: 'Val' } }]).cursor().exec();
// No need to `await` on the cursor or wait for a promise to resolve
cursor.eachAsync(doc => console.log(doc));

// Can also pass options to `cursor()`
const cursorWithOptions = MyModel.
  aggregate([{ $match: { name: 'Val' } }]).
  cursor({ batchSize: 10 }).
  exec();

geoNear

MongoDB ドライバーがサポートしなくなったため、Model.geoNear() は削除されました

接続文字列の URI エンコードの必須化

MongoDB ドライバーの変更により、接続文字列は URI エンコードする必要があります。

そうでない場合、接続は不正な文字メッセージで失敗する可能性があります。

特定の文字を含むパスワード

影響を受ける文字の完全なリストをご覧ください。

アプリが多数の異なる接続文字列で使用されている場合、テストケースは合格する可能性がありますが、本番環境のパスワードは失敗する可能性があります。安全のため、すべての接続文字列をエンコードしてください。

エンコードされていない接続文字列を使い続けたい場合は、最も簡単な修正方法は、mongodb-uri モジュールを使用して接続文字列を解析し、適切にエンコードされたバージョンを生成することです。次のような関数を使用できます

const uriFormat = require('mongodb-uri');
function encodeMongoURI(urlString) {
  if (urlString) {
    const parsed = uriFormat.parse(urlString);
    urlString = uriFormat.format(parsed);
  }
  return urlString;
}

// Your un-encoded string.
const mongodbConnectString = 'mongodb://...';
mongoose.connect(encodeMongoURI(mongodbConnectString));

上記の関数は、既存の文字列がすでにエンコードされているかどうかに関係なく、安全に使用できます。

ドメインソケット

ドメインソケットは URI エンコードする必要があります。例:

// Works in mongoose 4. Does **not** work in mongoose 5 because of more
// stringent URI parsing.
const host = '/tmp/mongodb-27017.sock';
mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`);

// Do this instead
const host = encodeURIComponent('/tmp/mongodb-27017.sock');
mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`);

toObject() オプション

toObject() および toJSON() への options パラメータは、デフォルトを上書きするのではなく、マージします。

// Note the `toObject` option below
const schema = new Schema({ name: String }, { toObject: { virtuals: true } });
schema.virtual('answer').get(() => 42);
const MyModel = db.model('MyModel', schema);

const doc = new MyModel({ name: 'test' });
// In mongoose 4.x this prints "undefined", because `{ minimize: false }`
// overwrites the entire schema-defined options object.
// In mongoose 5.x this prints "42", because `{ minimize: false }` gets
// merged with the schema-defined options.
console.log(doc.toJSON({ minimize: false }).answer);

集計パラメータ

aggregate() はスプレッドを受け入れなくなり、集計パイプラインを配列として渡す必要があります。以下のコードは 4.x で動作していました

MyModel.aggregate({ $match: { isDeleted: false } }, { $skip: 10 }).exec(cb);

上記のコードは 5.x では動作しません$match ステージと $skip ステージを配列でラップする必要があります。

MyModel.aggregate([{ $match: { isDeleted: false } }, { $skip: 10 }]).exec(cb);

ブール値の型変換

デフォルトでは、mongoose 4 はエラーなしに任意の値がブール値に強制変換されていました。

// Fine in mongoose 4, would save a doc with `boolField = true`
const MyModel = mongoose.model('Test', new Schema({
  boolField: Boolean
}));

MyModel.create({ boolField: 'not a boolean' });

Mongoose 5 は、次の値のみを true にキャストします

  • true
  • 'true'
  • 1
  • '1'
  • 'yes'

また、次の値を false にキャストします

  • false
  • 'false'
  • 0
  • '0'
  • 'no'

他のすべての値は CastError を引き起こします

クエリの型変換

update()updateOne()updateMany()replaceOne()remove()deleteOne()、および deleteMany() のキャストは、exec() まで実行されません。これにより、フックとカスタムクエリヘルパーがデータを変更しやすくなります。Mongoose はフックとクエリヘルパーが実行されるまで、渡されたデータを再構築しないためです。また、更新を渡したoverwrite オプションを設定することもできます。

// In mongoose 4.x, this becomes `{ $set: { name: 'Baz' } }` despite the `overwrite`
// In mongoose 5.x, this overwrite is respected and the first document with
// `name = 'Bar'` will be replaced with `{ name: 'Baz' }`
User.where({ name: 'Bar' }).update({ name: 'Baz' }).setOptions({ overwrite: true });

Post Save フックのフロー制御

Post フックはフロー制御を取得するようになりました。これは、非同期 Post Save フックと子ドキュメントの Post Save フックが、save() コールバックのに実行されることを意味します。

const ChildModelSchema = new mongoose.Schema({
  text: {
    type: String
  }
});
ChildModelSchema.post('save', function(doc) {
  // In mongoose 5.x this will print **before** the `console.log()`
  // in the `save()` callback. In mongoose 4.x this was reversed.
  console.log('Child post save');
});
const ParentModelSchema = new mongoose.Schema({
  children: [ChildModelSchema]
});

const Model = mongoose.model('Parent', ParentModelSchema);
const m = new Model({ children: [{ text: 'test' }] });
m.save(function() {
  // In mongoose 5.xm this prints **after** the "Child post save" message.
  console.log('Save callback');
});

$pushAll オペレーター

$pushAll は、MongoDB 2.4 以降非推奨になったため、save() では内部的にサポートされなくなり、使用されなくなりました。代わりに、$each を使用した $push を使用してください。

常にフォワードキーの順序を使用する

retainKeyOrder オプションは削除されました。Mongoose は、オブジェクトを複製するときに常に同じキー位置を保持するようになりました。キーの逆順に依存するクエリまたはインデックスがある場合は、それらを変更する必要があります。

クエリでセッターを実行する

セッターはデフォルトでクエリで実行されるようになり、以前の runSettersOnQuery オプションは削除されました。

const schema = new Schema({
  email: { type: String, lowercase: true }
});
const Model = mongoose.model('Test', schema);
Model.find({ email: 'FOO@BAR.BAZ' }); // Converted to `find({ email: 'foo@bar.baz' })`

プリコンパイル済みのブラウザバンドル

ブラウザー向けのプリコンパイル済みの Mongoose バージョンはなくなりました。ブラウザーで Mongoose スキーマを使用したい場合は、browserify/webpack を使用して独自のバンドルを構築する必要があります。

保存エラー

saveErrorIfNotFound オプションは削除されました。Mongoose は、基になるドキュメントが見つからなかった場合、save() から常にエラーを返すようになりました

Init フックのシグネチャ

init フックは完全に同期され、パラメータとして next() を受け取らなくなりました。

Document.prototype.init() は、パラメータとしてコールバックを受け取らなくなりました。常に同期でしたが、レガシー上の理由でコールバックがありました。

numAffectedsave()

doc.save() は、コールバックへの 3 番目のパラメータとして numAffected を渡さなくなりました。

remove() とデバウンス

doc.remove() はデバウンスしなくなりました

getPromiseConstructor()

getPromiseConstructor() はなくなりました。mongoose.Promise を使用してください。

プリフックからのパラメータの受け渡し

Mongoose 5.x では、next() を使用してチェーン内の次のプリミドルウェアにパラメータを渡すことはできません。Mongoose 4 では、プリミドルウェアの next('Test') は、次のミドルウェアをパラメータとして「Test」で呼び出します。Mongoose 5.x では、このサポートが削除されました。

配列の required バリデーター

Mongoose 5 では、required バリデーターは値が配列であるかどうかのみを確認します。つまり、Mongoose 4 のように空の配列では失敗しません

デバッグ出力のデフォルトが stderr ではなく stdout に

Mongoose 5 では、デフォルトのデバッグ関数は console.error() の代わりに console.info() を使用してメッセージを表示します。

フィルタープロパティの上書き

Mongoose 4.x では、プリミティブであるフィルタープロパティをオブジェクトで上書きすると、エラーなしで失敗しました。たとえば、次のコードは where() を無視し、Sport.find({ name: 'baseball' }) と同等になります

Sport.find({ name: 'baseball' }).where({ name: { $ne: 'softball' } });

Mongoose 5.x では、上記のコードは 'baseball'{ $ne: 'softball' } で正しく上書きします

bulkWrite() の結果

Mongoose 5.x は、MongoDB Node.js ドライバーのバージョン 3.x を使用します。MongoDB ドライバー 3.x は、bulkWrite() 呼び出しの結果の形式を変更したため、トップレベルの nInsertednModified などのプロパティはなくなりました。新しい結果オブジェクト構造はこちらで説明しています。

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

const res = await Model.bulkWrite([{ insertOne: { document: { name: 'test' } } }]);

console.log(res);

Mongoose 4.x では、上記は次のように出力します

BulkWriteResult {
  ok: [Getter],
  nInserted: [Getter],
  nUpserted: [Getter],
  nMatched: [Getter],
  nModified: [Getter],
  nRemoved: [Getter],
  getInsertedIds: [Function],
  getUpsertedIds: [Function],
  getUpsertedIdAt: [Function],
  getRawResponse: [Function],
  hasWriteErrors: [Function],
  getWriteErrorCount: [Function],
  getWriteErrorAt: [Function],
  getWriteErrors: [Function],
  getLastOp: [Function],
  getWriteConcernError: [Function],
  toJSON: [Function],
  toString: [Function],
  isOk: [Function],
  insertedCount: 1,
  matchedCount: 0,
  modifiedCount: 0,
  deletedCount: 0,
  upsertedCount: 0,
  upsertedIds: {},
  insertedIds: { '0': 5be9a3101638a066702a0d38 },
  n: 1 }

Mongoose 5.x では、スクリプトは次のように出力します

BulkWriteResult {
  result: 
  { ok: 1,
    writeErrors: [],
    writeConcernErrors: [],
    insertedIds: [ [Object] ],
    nInserted: 1,
    nUpserted: 0,
    nMatched: 0,
    nModified: 0,
    nRemoved: 0,
    upserted: [],
    lastOp: { ts: [Object], t: 1 } },
  insertedCount: 1,
  matchedCount: 0,
  modifiedCount: 0,
  deletedCount: 0,
  upsertedCount: 0,
  upsertedIds: {},
  insertedIds: { '0': 5be9a1c87decfc6443dd9f18 },
  n: 1 }

厳格な SSL 検証

MongoDB Node.js ドライバーの最新バージョンはデフォルトで厳格な SSL 検証を使用するため、自己署名証明書を使用している場合にエラーが発生する可能性があります。

アップグレードを妨げている場合は、tlsInsecure オプションを true に設定できます。

mongoose.connect(uri, { tlsInsecure: false }); // Opt out of additional SSL validation