よくある質問


Q. `localhost`への接続時に`connect ECONNREFUSED ::1:27017`というエラーが発生します。なぜですか?

`localhost`を`127.0.0.1`に置き換えるのが簡単な解決策です。

このエラーが発生する理由は、Node.js 18以降ではデフォルトでIPv6アドレスがIPv4アドレスよりも優先されるためです。また、ほとんどのLinuxとOSXマシンでは、デフォルトで`/etc/hosts`に`::1 localhost`エントリがあります。つまり、Node.js 18は`localhost`がIPv6の`::1`アドレスを意味するとみなします。そして、MongoDBはデフォルトではIPv6接続を受け入れません。

MongoDBサーバーでIPv6サポートを有効にすることでも、このエラーを修正できます。


Q. 操作`...`が10000ms後にタイムアウトしました。何が原因ですか?

A. 本質的に、この問題はMongoDBに接続していないことが原因です。MongooseはMongoDBに接続する前に使用できますが、いずれかの時点で接続する必要があります。例えば

await mongoose.createConnection(mongodbUri).asPromise();

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

await Test.findOne(); // Will throw "Operation timed out" error because didn't call `mongoose.connect()`
await mongoose.connect(mongodbUri);

const db = mongoose.createConnection();

const Test = db.model('Test', schema);

await Test.findOne(); // Will throw "Operation timed out" error because `db` isn't connected to MongoDB

Q. ローカルでは接続できますが、MongoDB Atlasに接続しようとするとこのエラーが発生します。何が原因ですか?

Mongooseが接続できるように、mongodbでIPアドレスをホワイトリストに登録する必要があります。`0.0.0.0/0`を使用してすべてのIPからのアクセスを許可できます。


Q. x.$__yは関数ではありません。何が原因ですか?

A. この問題は、互換性のない複数のバージョンのmongooseがインストールされていることが原因です。`npm list | grep "mongoose"`を実行して、問題を見つけて解決してください。スキーマまたはモデルを別のnpmパッケージに格納している場合は、別のパッケージの`dependencies`ではなく`peerDependencies`にMongooseをリストしてください。


Q. スキーマのプロパティを`unique`として宣言しましたが、それでも重複を保存できます。何が原因ですか?

A. Mongooseは`unique`を単独で処理しません。`{ name: { type: String, unique: true } }`は、``name`にMongoDBの一意のインデックスを作成する`ための単なる省略記法です。たとえば、MongoDBに`name`の一意のインデックスがまだない場合、`unique`がtrueであるにもかかわらず、以下のコードはエラーになりません。

const schema = new mongoose.Schema({
  name: { type: String, unique: true }
});
const Model = db.model('Test', schema);

// No error, unless index was already built
await Model.create([{ name: 'Val' }, { name: 'Val' }]);

ただし、`Model.on('index')`イベントを使用してインデックスの構築を待機すると、重複を保存しようとしたときに正しくエラーが発生します。

const schema = new mongoose.Schema({
  name: { type: String, unique: true }
});
const Model = db.model('Test', schema);

// Wait for model's indexes to finish. The `init()`
// function is idempotent, so don't worry about triggering an index rebuild.
await Model.init();

// Throws a duplicate key error
await Model.create([{ name: 'Val' }, { name: 'Val' }]);

MongoDBはインデックスを永続化するため、新しいデータベースから開始する場合、または`db.dropDatabase()`を実行した場合にのみ、インデックスを再構築する必要があります。本番環境では、Mongooseに依存するのではなく、MongoDBシェルを使用してインデックスを作成する必要があります。スキーマの`unique`オプションは開発とドキュメント作成に便利ですが、mongooseはインデックス管理ソリューションではありません


Q. スキーマにネストされたプロパティがある場合、mongooseはデフォルトで空のオブジェクトを追加します。なぜですか?

const schema = new mongoose.Schema({
  nested: {
    prop: String
  }
});
const Model = db.model('Test', schema);

// The below prints `{ _id: /* ... */, nested: {} }`, mongoose assigns
// `nested` to an empty object `{}` by default.
console.log(new Model());

A. これはパフォーマンスの最適化です。これらの空のオブジェクトはデータベースに保存されず、結果の`toObject()`にも含まれず、`minimize`オプションをオフにしない限り、`JSON.stringify()`出力にも表示されません。

この動作の理由は、Mongooseの変更検出とゲッター/セッターが`Object.defineProperty()`に基づいているためです。ドキュメントが作成されるたびに`Object.defineProperty()`を実行するオーバーヘッドをかけずに、ネストされたプロパティの変更検出をサポートするために、mongooseはモデルがコンパイルされるときに`Model`プロトタイプにプロパティを定義します。mongooseは`nested.prop`のゲッターとセッターを定義する必要があるため、`nested`は、基になるPOJOで`nested`が未定義であっても、常にmongooseドキュメントでオブジェクトとして定義する必要があります。


Q. 仮想プロパティミドルウェアゲッター/セッター、またはメソッドにアロー関数を使用しており、`this`の値が間違っています。

A. アロー関数は従来の関数とは異なる方法で`this`キーワードを処理します。Mongooseのゲッター/セッターは`this`に依存して、書き込んでいるドキュメントにアクセスできますが、この機能はアロー関数では機能しません。ゲッター/セッターでドキュメントにアクセスするつもりがない場合を除き、mongooseのゲッター/セッターにはアロー関数を使用しないでください。

// Do **NOT** use arrow functions as shown below unless you're certain
// that's what you want. If you're reading this FAQ, odds are you should
// just be using a conventional function.
const schema = new mongoose.Schema({
  propWithGetter: {
    type: String,
    get: v => {
      // Will **not** be the doc, do **not** use arrow functions for getters/setters
      console.log(this);
      return v;
    }
  }
});

// `this` will **not** be the doc, do **not** use arrow functions for methods
schema.method.arrowMethod = () => this;
schema.virtual('virtualWithArrow').get(() => {
  // `this` will **not** be the doc, do **not** use arrow functions for virtuals
  console.log(this);
});

Q. 次のような`type`という名前の埋め込みプロパティがあります

const holdingSchema = new Schema({
  // You might expect `asset` to be an object that has 2 properties,
  // but unfortunately `type` is special in mongoose so mongoose
  // interprets this schema to mean that `asset` is a string
  asset: {
    type: String,
    ticker: String
  }
});

しかし、`asset`オブジェクトを持つ`Holding`を保存しようとすると、mongooseはオブジェクトを文字列にキャストできないことを示すCastErrorを返します。なぜですか?

Holding.create({ asset: { type: 'stock', ticker: 'MDB' } }).catch(error => {
  // Cast to String failed for value "{ type: 'stock', ticker: 'MDB' }" at path "asset"
  console.error(error);
});

A. `type`プロパティはmongooseでは特別なため、`type: String`と指定すると、mongooseはそれを型宣言として解釈します。上記のスキーマでは、mongooseは`asset`をオブジェクトではなく文字列だと認識します。代わりにこれを実行してください

const holdingSchema = new Schema({
  // This is how you tell mongoose you mean `asset` is an object with
  // a string property `type`, as opposed to telling mongoose that `asset`
  // is a string.
  asset: {
    type: { type: String },
    ticker: String
  }
});

Q. 以下のコードのように、配列の下にあるネストされたプロパティをpopulateしています

new Schema({
  arr: [{
    child: { ref: 'OtherModel', type: Schema.Types.ObjectId }
  }]
});

`.populate({ path: 'arr.child', options: { sort: 'name' } })`は`arr.child.name`でソートされませんか?

A. このGitHubの問題を参照してください。既知の問題ですが、修正が非常に困難です。


Q. モデルに対するすべての関数呼び出しがハングします。何が間違っていますか?

A. デフォルトでは、mongooseはMongoDBに接続できるまで関数呼び出しをバッファリングします。詳細については、接続ドキュメントのバッファリングセクションを参照してください。


Q. どのようにデバッグを有効にできますか?

A. `debug`オプションを設定します

// all executed methods log output to console
mongoose.set('debug', true);

// disable colors in debug mode
mongoose.set('debug', { color: false });

// get mongodb-shell friendly output (ISODate)
mongoose.set('debug', { shell: true });

その他のデバッグオプション(ストリーム、コールバック)については、`.set()`の 'debug' オプションを参照してください。


Q. `save()`コールバックが実行されません。何が間違っていますか?

A. すべての`collection`アクション(挿入、削除、クエリなど)は、MongooseがMongoDBに正常に接続するまでキューに入れられます。Mongooseの`connect()`または`createConnection()`関数をまだ呼び出していない可能性があります。

Mongoose 5.11には、`bufferTimeoutMS`オプション(デフォルトで10000に設定)があり、操作がバッファリングされたままエラーをスローするまでの時間を構成します。

アプリケーション全体でMongooseのバッファリングメカニズムをオプトアウトする場合は、グローバル`bufferCommands`オプションをfalseに設定します

mongoose.set('bufferCommands', false);

Mongooseのバッファリングメカニズムをオプトアウトする代わりに、`bufferTimeoutMS`を短くして、Mongooseが短時間だけバッファリングするようにすることもできます。

// If an operation is buffered for more than 500ms, throw an error.
mongoose.set('bufferTimeoutMS', 500);

Q. 各データベース操作ごとに新しい接続を作成/破棄する必要がありますか?

A. いいえ。アプリケーションの起動時に接続を開き、アプリケーションのシャットダウンまで開いたままにします。


Q. nodemon/テストフレームワークを使用すると、「OverwriteModelError: コンパイル済みモデルは上書きできません...」というエラーが発生するのはなぜですか?

A. `mongoose.model('ModelName', schema)`では、'ModelName'を一意にする必要があります。そのため、`mongoose.model('ModelName')`を使用してモデルにアクセスできます。mochaの`beforeEach()`フックに`mongoose.model('ModelName', schema);`を配置すると、このコードはすべてのテストの前に'ModelName'という名前の新しいモデルを作成しようとします。そのため、エラーが発生します。指定された名前の新しいモデルは一度だけ作成してください。同じ名前の複数のモデルを作成する必要がある場合は、新しい接続を作成し、モデルを接続にバインドします。

const mongoose = require('mongoose');
const connection = mongoose.createConnection(/* ... */);

// use mongoose.Schema
const kittySchema = mongoose.Schema({ name: String });

// use connection.model
const Kitten = connection.model('Kitten', kittySchema);

Q. ドキュメント作成時に実際のデータが必要になるように、空の配列に配列パスを初期化するmongooseのデフォルトの動作を変更するにはどうすればよいですか?

A. 配列のデフォルトを`undefined`に設定できます。

const CollectionSchema = new Schema({
  field1: {
    type: [String],
    default: void 0
  }
});

Q. 配列パスを`null`に初期化するにはどうすればよいですか?

A. 配列のデフォルトを`null`に設定できます。

const CollectionSchema = new Schema({
  field1: {
    type: [String],
    default: null
  }
});

Q. 日付を扱う場合、集約`$match`がfindクエリが返すドキュメントを返さないのはなぜですか?

A. Mongooseは集約パイプラインステージをキャストしません。これは、`$project`、`$group`などでは、プロパティの型が集約中に変更される可能性があるためです。集約フレームワークを使用して日付でクエリする場合は、有効な日付を渡していることを確認する責任があります。


Q. 日付オブジェクトのインプレース変更(例:`date.setMonth(1);`)が保存されないのはなぜですか?

doc.createdAt.setDate(2011, 5, 1);
doc.save(); // createdAt changes won't get saved!

A. Mongooseは現在、日付オブジェクトのインプレース更新を監視しません。この機能が必要な場合は、このGitHubの問題で自由に議論してください。いくつかの回避策があります。

doc.createdAt.setDate(2011, 5, 1);
doc.markModified('createdAt');
doc.save(); // Works

doc.createdAt = new Date(2011, 5, 1).setHours(4);
doc.save(); // Works

Q. 同一のドキュメントに対してsave()を複数回並列で呼び出すと、最初の保存呼び出しだけが成功し、残りはParallelSaveErrorsを返すのはなぜですか?

A. 一般的な検証やミドルウェアの非同期性により、同一のドキュメントに対してsave()を複数回並列で呼び出すと、競合が発生する可能性があります。例えば、同じパスを検証してから無効にするといった場合です。


Q. なぜ、12文字の文字列であれば何でもObjectIdにキャストできるのですか?

A. 技術的には、12文字の文字列であれば、ObjectIdとして有効です。文字列が正確に24個の16進数文字であるかどうかをテストするには、/^[a-f0-9]{24}$/のような正規表現を使用することを検討してください。


Q. MongooseのMapのキーは文字列でなければならないのはなぜですか?

A. Mapは最終的にMongoDBに保存され、そのキーは文字列でなければならないためです。


Q. limitオプションを使用してModel.find(...).populate(...)を使用していますが、limitよりも少ない結果が得られています。なぜですか?

A. findクエリから返された各ドキュメントに対して個別のクエリを実行することを避けるために、Mongooseは(numDocuments * limit)をlimitとしてクエリを実行します。正しいlimitが必要な場合は、perDocumentLimitオプション(Mongoose 5.9.0の新機能)を使用する必要があります。ただし、populate()は各ドキュメントに対して個別のクエリを実行することに注意してください。


Q. クエリ/更新が2回実行されているようです。なぜですか?

A. クエリが重複する最も一般的な原因は、クエリでコールバックとプロミスを混在させていることです。これは、find()updateOne()などのクエリ関数にコールバックを渡すと、クエリがすぐに実行され、then()を呼び出すとクエリが再度実行されるためです。

プロミスとコールバックを混在させると、配列に重複したエントリが作成される可能性があります。例えば、以下のコードはtags配列に1つではなく*2つの*エントリを挿入します。

const BlogPost = mongoose.model('BlogPost', new Schema({
  title: String,
  tags: [String]
}));

// Because there's both `await` **and** a callback, this `updateOne()` executes twice
// and thus pushes the same string into `tags` twice.
const update = { $push: { tags: ['javascript'] } };
await BlogPost.updateOne({ title: 'Introduction to Promises' }, update, (err, res) => {
  console.log(res);
});

何か追加するものはありますか?

このページに貢献したい場合は、GitHub上のページにアクセスし、編集ボタンを使用してプルリクエストを送信してください。