FAQ


Q. I get an error connect ECONNREFUSED ::1:27017 when connecting to localhost. Why?

The easy solution is to replace localhost with 127.0.0.1.

The reason why this error happens is that Node.js 18 and up prefer IPv6 addresses over IPv4 by default. And, most Linux and OSX machines have a ::1 localhost entry in /etc/hosts by default. That means that Node.js 18 will assume that localhost means the IPv6 ::1 address. And MongoDB doesn't accept IPv6 connections by default.

You can also fix this error by enabling IPv6 support on your MongoDB server.


Q. Operation ... timed out after 10000 ms. What gives?

A. At its core, this issue stems from not connecting to MongoDB. You can use Mongoose before connecting to MongoDB, but you must connect at some point. For example:

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. I am able to connect locally but when I try to connect to MongoDB Atlas I get this error. What gives?

You must ensure that you have whitelisted your ip on mongodb to allow Mongoose to connect. You can allow access from all ips with 0.0.0.0/0.


Q. x.$__y is not a function. What gives?

A. This issue is a result of having multiple versions of mongoose installed that are incompatible with each other. Run npm list | grep "mongoose" to find and remedy the problem. If you're storing schemas or models in a separate npm package, please list Mongoose in peerDependencies rather than dependencies in your separate package.


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.

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

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

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 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 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?

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. 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 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.
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. 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. 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:

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

For more debugging options (streams, callbacks), see the 'debug' option under .set().


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

A. All collection actions (insert, remove, queries, etc.) are queued until Mongoose successfully connects to MongoDB. It is likely you haven't called Mongoose's connect() or createConnection() function yet.

In Mongoose 5.11, there is a bufferTimeoutMS option (set to 10000 by default) that configures how long Mongoose will allow an operation to stay buffered before throwing an error.

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

Instead of opting out of Mongoose's buffering mechanism, you may want to instead reduce bufferTimeoutMS to make Mongoose only buffer for a short time.

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

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.

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. 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 undefined.

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

Q. How can I initialize an array path to null?

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

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

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.


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. Why does calling save() multiple times on the same document in parallel only let the first save call succeed and return ParallelSaveErrors for the rest?

A. Due to the asynchronous nature of validation and middleware in general, calling save() multiple times in parallel on the same doc could result in conflicts. For example, validating, and then subsequently invalidating the same path.


Q. Why is any 12 character string successfully cast to an ObjectId?

A. Technically, any 12 character string is a valid ObjectId. Consider using a regex like /^[a-f0-9]{24}$/ to test whether a string is exactly 24 hex characters.


Q. Why do keys in Mongoose Maps have to be strings?

A. Because the Map eventually gets stored in MongoDB where the keys must be strings.


Q. I am using Model.find(...).populate(...) with the limit option, but getting fewer results than the limit. What gives?

A. In order to avoid executing a separate query for each document returned from the find query, Mongoose instead queries using (numDocuments * limit) as the limit. If you need the correct limit, you should use the perDocumentLimit option (new in Mongoose 5.9.0). Just keep in mind that populate() will execute a separate query for each document.


Q. My query/update seems to execute twice. Why is this happening?

A. The most common cause of duplicate queries is mixing callbacks and promises with queries. That's because passing a callback to a query function, like find() or updateOne(), immediately executes the query, and calling then() executes the query again.

Mixing promises and callbacks can lead to duplicate entries in arrays. For example, the below code inserts 2 entries into the tags array, *not just 1.

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

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.