Mongoose
Mongoose
Getting Started
----------------------------------------------
# install Mongoose
$ npm install mongoose --save
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
// we're connected!
});
# save to db
fluffy.save(function (err, fluffy) {
if (err) return console.error(err);
fluffy.speak();
});
==============================================
Schemas
----------------------------------------------
- Schema definition
- Creating a model
- Ids
- Instance methods
- Statics
- Query Helpers
- Indexes
- Virtuals
- Aliases
- Options
String
Number
Date
Buffer
Boolean
Mixed
ObjectId
Array
Decimal128
Map
When create new document Mongoose add _id of type ObjectId to it.
const Model = mongoose.model('Test', schema);
const doc = new Model();
doc._id instanceof mongoose.Types.ObjectId; // true
doc._id = 1;
await doc.save(); // works
// define a schema
const animalSchema = new Schema({ name: String, type: String });
Do not declare statics using ES6 arrow functions (=>). Arrow functions explicitly
prevent binding this, so the above examples will not work because of the value of
this.
animalSchema.query.byName = function(name) {
return this.where({ name: new RegExp(name, 'i') })
};
const Animal = mongoose.model('Animal', animalSchema);
When your application starts up, Mongoose automatically calls createIndex for each
defined index in your schema. Mongoose will call createIndex for each index
sequentially, and emit an 'index' event on the model when all the createIndex calls
succeeded or when there was an error. While nice for development, it is recommended
this behavior be disabled in production since index creation can cause a
significant performance impact. Disable the behavior by setting the autoIndex
option of your schema to false, or globally on the connection by setting the option
autoIndex to false.
mongoose.connect('mongodb://user:pass@localhost:port/database', { autoIndex:
false });
// or
mongoose.createConnection('mongodb://user:pass@localhost:port/database',
{ autoIndex: false });
// or
animalSchema.set('autoIndex', false);
// or
new Schema({..}, { autoIndex: false });
Mongoose will emit an index event on the model when indexes are done building or an
error occurred.
// Will cause an error because mongodb has an _id index by default that
// is not sparse
animalSchema.index({ _id: 1 }, { sparse: true });
const Animal = mongoose.model('Animal', animalSchema);
// define a schema
const personSchema = new Schema({
name: {
first: String,
last: String
}
});
// create a document
const person = new Person({
name: { first: 'Jhon', last: 'Doe' }
});
# concatenating properties
console.log(person.name.first + ' ' + person.name.last); // Jhon Doe
# perform with virtual method
personSchema.virtual('fullName').get(function() {
return this.name.first + ' ' + this.name.last;
});
console.log(axl.fullName); // Jhon Doe
// or
# option: autoIndex
By default, Mongoose's init() function creates all the indexes defined in your
model's schema by calling Model.createIndexes() after you successfully connect to
MongoDB.
# option: autoCreate
Before Mongoose builds indexes, it calls Model.createCollection() to create the
underlying collection in MongoDB if autoCreate is set to true.
# option: bufferCommands
By default, mongoose buffers commands when the connection goes down until the
driver manages to reconnect. To disable buffering, set bufferCommands to false.
# option: capped
Mongoose supports MongoDBs capped collections. To specify the underlying MongoDB
collection be capped, set the capped option to the maximum size of the collection
in bytes.
new Schema({..}, { capped: { size: 1024, max: 1000, autoIndexId: true } });
# option: collection
Mongoose by default produces a collection name by passing the model name to the
utils.toCollectionName method. This method pluralizes the name. Set this option if
you need a different name for your collection.
When you define a discriminator, Mongoose adds a path to your schema that stores
which discriminator a document is an instance of. By default, Mongoose adds an __t
path, but you can set discriminatorKey to overwrite this default.
# option: id
Mongoose assigns each of your schemas an id virtual getter by default which returns
the document's _id field cast to a string, or in the case of ObjectIds, its
hexString. If you don't want an id getter added to your schema, you may disable it
by passing this option at schema construction time.
// default behavior
const schema = new Schema({ name: String });
const Page = mongoose.model('Page', schema);
const p = new Page({ name: 'mongodb.org' });
console.log(p.id); // '50341373e894ad16347efe01'
// disabled id
const schema = new Schema({ name: String }, { id: false });
const Page = mongoose.model('Page', schema);
const p = new Page({ name: 'mongodb.org' });
console.log(p.id); // undefined
You can only use this option on subdocuments. Mongoose can't save a document
without knowing its id, so you will get an error if you try to save a document
without an _id.
// default behavior
const schema = new Schema({ name: String });
const Page = mongoose.model('Page', schema);
const p = new Page({ name: 'mongodb.org' });
console.log(p); // { _id: '50341373e894ad16347efe01', name: 'mongodb.org' }
// disabled _id
const childSchema = new Schema({ name: String }, { _id: false });
const parentSchema = new Schema({ children: [childSchema] });
This behavior can be overridden by setting minimize option to false. It will then
store empty objects.
const schema = new Schema({ name: String, inventory: {} }, { minimize: false });
const Character = mongoose.model('Character', schema);
sam.inventory.barrowBlade = 1;
sam.$isEmpty('inventory'); // false
// set to false..
const thingSchema = new Schema({..}, { strict: false });
const thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // iAmNotInTheSchema is now saved to the db!!
This also affects the use of doc.set() to set a property value.
This value can be overridden at the model instance level by passing a second
boolean argument:
const Thing = mongoose.model('Thing');
const thing = new Thing(doc, true); // enables strict mode
const thing = new Thing(doc, false); // disables strict mode
The strict option may also be set to "throw" which will cause errors to be produced
instead of dropping the bad data.
NOTE: Any key/val set on the instance that does not exist in your schema is always
ignored, regardless of schema option.
To have all virtuals show up in your console.log output, set the toObject option to
{ getters: true }:
// customized versionKey
new Schema({..}, { versionKey: '_somethingElse' })
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'mongoose v3' });
thing.save(); // { _somethingElse: 0, name: 'mongoose v3' }
Note that Mongoose's default versioning is not a full optimistic concurrency
solution. Mongoose's default versioning only operates on arrays as shown below.
Document versioning can also be disabled by setting the versionKey to false. DO NOT
disable versioning unless you know what you are doing.
Mongoose only updates the version key when you use save(). If you use update(),
findOneAndUpdate(), etc. Mongoose will not update the version key. As a workaround,
you can use the below middleware.
schema.pre('findOneAndUpdate', function() {
const update = this.getUpdate();
if (update.__v != null) {
delete update.__v;
}
const keys = ['$set', '$setOnInsert'];
for (const key of keys) {
if (update[key] != null && update[key].__v != null) {
delete update[key].__v;
if (Object.keys(update[key]).length === 0) {
delete update[key];
}
}
}
update.$inc = update.$inc || {};
update.$inc.__v = 1;
});
For example, suppose you have a House model that contains a list of photos, and a
status that represents whether this house shows up in searches. Suppose that a
house that has status 'APPROVED' must have at least two photos. You might implement
the logic of approving a house document as shown below:
house.status = 'APPROVED';
await house.save();
}
The markApproved() function looks right in isolation, but there might be a
potential issue: what if another function removes the house's photos between the
findOne() call and the save() call? For example, the below code will succeed:
The timestamps option tells mongoose to assign createdAt and updatedAt fields to
your schema. The type assigned is Date.
By default, the names of the fields are createdAt and updatedAt. Customize the
field names by setting timestamps.createdAt and timestamps.updatedAt.
By default, Mongoose uses new Date() to get the current time. If you want to
overwrite the function Mongoose uses to get the current time, you can set the
timestamps.currentTime option. Mongoose will call the timestamps.currentTime
function whenever it needs to get the current time.
const schema = Schema({
createdAt: Number,
updatedAt: Number,
name: String
}, {
// Make Mongoose use Unix time (seconds since Jan 1, 1970)
timestamps: { currentTime: () => Math.floor(Date.now() / 1000) }
});
==============================================
Connection
----------------------------------------------
Buffering
Error Handling
Options
Connection String Options
Connection Events
A note about keepAlive
Server Selection
Replica Set Connections
Replica Set Host Names
Multi-mongos support
Multiple connections
Connection Pools
Option Changes in v5.x
// Works
MyModel.findOne(function(error, result) { /* ... */ });
That's because mongoose buffers model function calls internally. This buffering is
convenient, but also a common source of confusion. Mongoose will not throw any
errors by default if you use a model without connecting.
setTimeout(function() {
mongoose.connect('mongodb://localhost:27017/myapp', {useNewUrlParser: true});
}, 60000);
To disable buffering, turn off the bufferCommands option on your schema. If you
have bufferCommands on and your connection is hanging, try turning bufferCommands
off to see if you haven't opened a connection properly. You can also disable
bufferCommands globally:
mongoose.set('bufferCommands', false);
Note that buffering is also responsible for waiting until Mongoose creates
collections if you use the autoCreate option. If you disable buffering, you should
also disable the autoCreate option and use createCollection() to create capped
collections or collections with collations.
// Or:
try {
await mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser:
true });
} catch (error) {
handleError(error);
}
To handle errors after initial connection was established, you should listen for
error events on the connection. However, you still need to handle initial
connection errors as shown above.
Options
The connect method also accepts an options object which will be passed on to the
underlying MongoDB driver.
mongoose.connect(uri, options);
A full list of options can be found on the MongoDB Node.js driver docs for
connect(). Mongoose passes options to the driver without modification, modulo a few
exceptions that are explained below.
Example:
const options = {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false,
autoIndex: false, // Don't build indexes
poolSize: 10, // Maintain up to 10 socket connections
serverSelectionTimeoutMS: 5000, // Keep trying to send operations for 5 seconds
socketTimeoutMS: 45000, // Close sockets after 45 seconds of inactivity
family: 4 // Use IPv4, skip trying IPv6
};
mongoose.connect(uri, options);
See this page for more information about connectTimeoutMS and socketTimeoutMS
Callback
The connect() function also accepts a callback parameter and returns a promise.
// Or using promises
mongoose.connect(uri, options).then(
() => { /** ready to use. The `mongoose.connect()` promise resolves to mongoose
instance. */ },
err => { /** handle initial connection error */ }
);
Connection String Options
You can also specify driver options in your connection string as parameters in the
query string portion of the URI. This only applies to options passed to the MongoDB
driver. You can't set Mongoose-specific options like bufferCommands in the query
string.
mongoose.connect('mongodb://localhost:27017/test?
connectTimeoutMS=1000&bufferCommands=false&authSource=otherdb');
// The above is equivalent to:
mongoose.connect('mongodb://localhost:27017/test', {
connectTimeoutMS: 1000
// Note that mongoose will **not** pull `bufferCommands` from the query string
});
The disadvantage of putting options in the query string is that query string
options are harder to read. The advantage is that you only need a single
configuration option, the URI, rather than separate options for socketTimeoutMS,
connectTimeoutMS, etc. Best practice is to put options that likely differ between
development and production, like replicaSet or ssl, in the connection string, and
options that should remain constant, like connectTimeoutMS or poolSize, in the
options object.
The MongoDB docs have a full list of supported connection string options. Below are
some options that are often useful to set in the connection string because they are
closely associated with the hostname and authentication information.
authSource - The database to use when authenticating with user and pass. In
MongoDB, users are scoped to a database. If you are getting an unexpected login
failure, you may need to set this option.
family - Whether to connect using IPv4 or IPv6. This option passed to Node.js'
dns.lookup() function. If you don't specify this option, the MongoDB driver will
try IPv6 first and then IPv4 if IPv6 fails. If your mongoose.connect(uri) call
takes a long time, try mongoose.connect(uri, { family: 4 })
Connection Events
Connections inherit from Node.js' EventEmitter class, and emit events when
something happens to the connection, like losing connectivity to the MongoDB
server. Below is a list of events that a connection may emit.
connecting: Emitted when Mongoose starts making its initial connection to the
MongoDB server
connected: Emitted when Mongoose successfully makes its initial connection to the
MongoDB server, or when Mongoose reconnects after losing connectivity.
open: Equivalent to connected
disconnecting: Your app called Connection#close() to disconnect from MongoDB
disconnected: Emitted when Mongoose lost connection to the MongoDB server. This
event may be due to your code explicitly closing the connection, the database
server crashing, or network connectivity issues.
close: Emitted after Connection#close() successfully closes the connection. If you
call conn.close(), you'll get both a 'disconnected' event and a 'close' event.
reconnected: Emitted if Mongoose lost connectivity to MongoDB and successfully
reconnected. Mongoose attempts to automatically reconnect when it loses connection
to the database.
error: Emitted if an error occurs on a connection, like a parseError due to
malformed data or a payload larger than 16MB.
fullsetup: Emitted when you're connecting to a replica set and Mongoose has
successfully connected to the primary and at least one secondary.
all: Emitted when you're connecting to a replica set and Mongoose has successfully
connected to all servers specified in your connection string.
reconnectFailed: Emitted when you're connected to a standalone server and Mongoose
has run out of reconnectTries. The MongoDB driver will no longer attempt to
reconnect after this event is emitted. This event will never be emitted if you're
connected to a replica set.
When you're connecting to a single MongoDB server (a "standalone"), Mongoose will
emit 'disconnected' if it gets disconnected from the standalone server, and
'connected' if it successfully connects to the standalone. In a replica set with
useUnifiedTopology = true, Mongoose will emit 'disconnected' if it loses
connectivity to every server in the replica set, and 'connected' if it manages to
reconnect to at least one server in the replica set.
mongoose.connect('mongodb://[username:password@]host1[:port1][,host2[:port2],...
[,hostN[:portN]]][/[database][?options]]' [, options]);
For example:
mongoose.connect('mongodb://user:pw@host1.com:27017,host2.com:27017,host3.com:27017
/testdb');
To connect to a single node replica set, specify the replicaSet option.
mongoose.connect('mongodb://host1:port1/?replicaSet=rsName');
Server Selection
If you enable the useUnifiedTopology option, the underlying MongoDB driver will use
server selection to connect to MongoDB and send operations to MongoDB. If the
MongoDB driver can't find a server to send an operation to after
serverSelectionTimeoutMS, you'll get the below error:
mongoose.connect(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
serverSelectionTimeoutMS: 5000 // Timeout after 5s instead of 30s
});
A MongoTimeoutError has a reason property that explains why server selection timed
out. For example, if you're connecting to a standalone server with an incorrect
password, reason will contain an "Authentication failed" error.
// Can get this error even if your connection string doesn't include
// `localhost` if `rs.conf()` reports that one replica set member has
// `localhost` as its host name.
failed to connect to server [localhost:27017] on first connect
If you're experiencing a similar error, connect to the replica set using the mongo
shell and run the rs.conf() command to check the host names of each replica set
member. Follow this page's instructions to change a replica set member's host name.
Multi-mongos support
You can also connect to multiple mongos instances for high availability in a
sharded cluster. You do not need to pass any special options to connect to multiple
mongos in mongoose 5.x.
You may need multiple connections to MongoDB for several reasons. One reason is if
you have multiple databases or multiple MongoDB clusters. Another reason is to work
around slow trains. The mongoose.createConnection() function takes the same
arguments as mongoose.connect() and returns a new connection.
// The alternative to the export model pattern is the export schema pattern.
module.exports = userSchema;
// Because if you export a model as shown below, the model will be scoped
// to Mongoose's default connection.
// module.exports = mongoose.model('User', userSchema);
If you use the export schema pattern, you still need to create models somewhere.
There are two common patterns. First is to export a connection and register the
models on the connection in the file:
// connections/fast.js
const mongoose = require('mongoose');
module.exports = conn;
// connections/slow.js
const mongoose = require('mongoose');
module.exports = conn;
Another alternative is to register connections with a dependency injector or
another inversion of control (IOC) pattern.
conn.model('User', require('../schemas/user'));
conn.model('PageView', require('../schemas/pageView'));
return conn;
};
Connection Pools
Each connection, whether created with mongoose.connect or mongoose.createConnection
are all backed by an internal configurable connection pool defaulting to a maximum
size of 5. Adjust the pool size using your connection options:
==============================================
Schemas and Models
----------------------------------------------
# Schema example:
schema --> compile to --> model
# example:
var mongoose = require( 'mongoose' ),
dbURI = 'mongodb://localhost/MongoosePM';
mongoose.connect(dbURI);
// Connection events snipped out for brevity
/* ********************************************
USER SCHEMA
******************************************** */
var userSchema = new mongoose.Schema({
name: String,
email: {type: String, unique:true},
createdOn: { type: Date, default: Date.now },
modifiedOn: Date,
lastLogin: Date
});
# instances
var user = new User({ name: 'Simon' });
console.log(user.name);
if(!err){
console.log('Saved user name: ' + user.name);
console.log('_id of saved user: ' + user._id);
}
});
==============================================
QueryBuilder
----------------------------------------------