FAQ 常见问题

优质
小牛编辑
131浏览
2023-12-01

Q. Why don't my changes to arrays get saved when I update an element directly?

doc.array[3] = 'changed';
doc.save();

A. Mongoose doesn't create getters/setters for array indexes; without them mongoose never gets notified of the change and so doesn't know to persist the new value. The work-around is to use MongooseArray#set available in Mongoose >= 3.2.0.

// 3.2.0
doc.array.set(3, 'changed');
doc.save();

// if running a version less than 3.2.0, you must mark the array modified before saving.
doc.array[3] = 'changed';
doc.markModified('array');
doc.save();

Q. I declared a schema property as unique but I can still save duplicates. What gives?

A. Mongoose doesn't handle unique on its own: { name: { type: String, unique: true } } is just a shorthand for creating a MongoDB unique index on name. For example, if MongoDB doesn't already have a unique index on name, the below code will not error despite the fact that unique is true.

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

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

However, if you wait for the index to build using the Model.on('index') event, attempts to save duplicates will correctly error.

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

Model.on('index', function(err) { // <-- Wait for model's indexes to finish
  assert.ifError(err);
  Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) {
    console.log(err);
  });
});

// Promise based alternative. `init()` returns a promise that resolves
// when the indexes have finished building successfully. The `init()`
// function is idempotent, so don't worry about triggering an index rebuild.
Model.init().then(function() {
  assert.ifError(err);
  Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) {
    console.log(err);
  });
});

MongoDB persists indexes, so you only need to rebuild indexes if you're starting with a fresh database or you ran db.dropDatabase(). In a production environment, you should [create your indexes using the MongoDB shell])(https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/) rather than relying on mongoose to do it for you. The unique option for schemas is convenient for development and documentation, but mongoose is not an index management solution.


Q. When I have a nested property in a schema, mongoose adds empty objects by default. Why?

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

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

A. This is a performance optimization. These empty objects are not saved to the database, nor are they in the result toObject(), nor do they show up in JSON.stringify() output unless you turn off the minimize option.

The reason for this behavior is that Mongoose's change detection and getters/setters are based on Object.defineProperty(). In order to support change detection on nested properties without incurring the overhead of running Object.defineProperty() every time a document is created, mongoose defines properties on the Model prototype when the model is compiled. Because mongoose needs to define getters and setters for nested.prop, nested must always be defined as an object on a mongoose document, even if nested is undefined on the underlying POJO.


Q. I'm using an arrow function for a virtual, middleware, getter/setter, or method and the value of this is wrong.

A. Arrow functions handle the this keyword much differently than conventional functions. Mongoose getters/setters depend on this to give you access to the document that you're writing to, but this functionality does not work with arrow functions. Do not use arrow functions for mongoose getters/setters unless do not intend to access the document in the getter/setter.

// 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.
var 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. I have an embedded property named type like this:

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
  }
});

But mongoose gives me a CastError telling me that it can't cast an object to a string when I try to save a Holding with an asset object. Why is this?

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. The type property is special in mongoose, so when you say type: String, mongoose interprets it as a type declaration. In the above schema, mongoose thinks asset is a string, not an object. Do this instead:

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. Why don't in-place modifications to date objects (e.g. date.setMonth(1);) get saved?

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

A. Mongoose currently doesn't watch for in-place updates to date objects. If you have need for this feature, feel free to discuss on this GitHub issue. There are several workarounds:

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. I'm populating a nested property under an array like the below code:

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

.populate({ path: 'arr.child', options: { sort: 'name' } }) won't sort by arr.child.name?

A. See this GitHub issue. It's a known issue but one that's exceptionally difficult to fix.


Q. All function calls on my models hang, what am I doing wrong?

A. By default, mongoose will buffer your function calls until it can connect to MongoDB. Read the buffering section of the connection docs for more information.


Q. How can I enable debugging?

A. Set the debug option to true:

mongoose.set('debug', true)

All executed collection methods will log output of their arguments to your console.


Q. My save() callback never executes. What am I doing wrong?

A. All collection actions (insert, remove, queries, etc.) are queued until the connection opens. It is likely that an error occurred while attempting to connect. Try adding an error handler to your connection.

// if connecting on the default mongoose connection
mongoose.connect(..);
mongoose.connection.on('error', handleError);

// if connecting on a separate connection
var conn = mongoose.createConnection(..);
conn.on('error', handleError);

If you want to opt out of mongoose's buffering mechanism across your entire application, set the global bufferCommands option to false:

mongoose.set('bufferCommands', false);

Q. Should I create/destroy a new connection for each database operation?

A. No. Open your connection when your application starts up and leave it open until the application shuts down.


Q. Why do I get "OverwriteModelError: Cannot overwrite .. model once compiled" when I use nodemon / a testing framework?

A. mongoose.model('ModelName', schema) requires 'ModelName' to be unique, so you can access the model by using mongoose.model('ModelName'). If you put mongoose.model('ModelName', schema); in a mocha beforeEach() hook, this code will attempt to create a new model named 'ModelName' before every test, and so you will get an error. Make sure you only create a new model with a given name once. If you need to create multiple models with the same name, create a new connection and bind the model to the connection.

var mongoose = require('mongoose');
var connection = mongoose.createConnection(..);

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

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

Q. How can I change mongoose's default behavior of initializing an array path to an empty array so that I can require real data on document creation?

A. You can set the default of the array to a function that returns null.

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

Q. Why does my aggregate $match fail to return the document that my find query returns when working with dates?

A. Mongoose does not cast aggregation pipeline stages because with $project, $group, etc. the type of a property may change during the aggregation. If you want to query by date using the aggregation framework, you're responsible for ensuring that you're passing in a valid date.

Something to add?

If you'd like to contribute to this page, please visit it on github and use the Edit button to send a pull request.