Mongoose: Gracefully model MongoDB objects in NodeJS.
We developed Mongoose because [developers] had trouble writing MongoDB’s validation mechanisms, type conversions, and business logic templates.
Mongoose offers a straightforward, pattern-based solution to the problem of modeling application data. This includes built-in type conversions, validators, query constructors, business logic hooks, and more.
Mongoose is positioned between MongoDB and NodeJS, which seems to add some complexity, but actually abstracts a lot, making it much easier to use MongoDB.
Project installation
We made project presentation based on KOA and cloned the following project address
https://github.com/daly-young/mongoosebasic.git
Copy the code
Run:
node demos/index.js
Copy the code
Schema | Model | Entity
Schema: A database model skeleton that is stored as a file and does not have the operation capability of a database
Model: A database action pair with abstract properties and behavior that is generated by a Schema publication
Entity: An Entity created by Model whose actions also affect the database
Keep in mind that Schema generates Model and Model creates Entity. Both Model and Entity can affect database operations, but Model is more operational than Entity.
Schema
Schema is a kind of data schema used in Mongoose. It can be understood as the definition of table structure. Each schema maps to a collection in mongodb, which does not have the ability to manipulate databases
To create the models folder in the root directory, we define a Schema for user and call it user.js
const UserSchema = new mongoose.Schema({
userName: String
})
Copy the code
Defining a Schema is as simple as specifying field names and types.
1—Schema.Type
Schema.Type is some data types determined by Mongoose, including basic data types, and it also has some schema. Type unique to Mongoose. Of course, you can also customize schema. Type. Only types that satisfy schema. Type can be defined in a Schema.
Schema Types built-in Types as follows: String, Number, Boolean | Bool, Array, Buffer, the Date, the ObjectId, Mixed
1.0—Buffer
An instance of the Buffer class is similar to an integer array, but buffers are fixed in size and allocate physical memory outside of the V8 heap. The size of a Buffer is determined when it is created and cannot be adjusted. The Buffer class is a global variable type that works directly with binary data. It can be built in a variety of ways.
Buffer and ArrayBuffer are two types of hidden objects in Nodejs. See NodeJs-API for more information
1.1 – the ObjectId
Declare an ObjectId type with schema.types.objectid. The object ID is a 24-bit Hash string of the same type as the _id in MongoDB.
const mongoose = require('mongoose')
const ObjectId = mongoose.Schema.Types.ObjectId
const Car = new Schema({ driver: ObjectId })
Copy the code
1.2—Mixed
Hybrid is a “store anything” data type whose flexibility comes from compromising maintainability. Mixed is defined with schema.types.Mixed or with a literal empty object {}. The following definition is equivalent:
const AnySchema = new Schema({any:{}})
const AnySchema = new Schema({any:Schema.Types.Mixed})
Copy the code
Mixed types can be modified arbitrarily because they have no specific constraints. Once the stereotype is modified, markModified() must be called
Person.anything = {x:[3,4,{y:'change'}]} person.markmodified ('anything') // input value, meaning that this value changes person.save(); // Change values are savedCopy the code
2—Validation
Data storage needs to be verified. Not all data can be thrown into the database or displayed to the client. Data verification needs to remember the following rules:
- Validation is always defined in SchemaType
- Validation is an internal middleware
- Validation is enabled by default when a Document is saved, unless you turn it off
- Validation is asynchronous and recursive, and if your SubDoc validation fails, the Document will not be saved
- Validation does not care about error types, but is accessible through the ValidationError object
2.1– Validator ####=
Required Non-null Verification MIN/Max Range Verification (boundary value verification) enum/match Enumeration Verification/Matching verification VALIDATE A user-defined verification rule
The following is a comprehensive case study:
Var PersonSchema = new Schema({name:{type:'String', required:true // Name not empty}, age:{type:'Nunmer', min:18, // age minimum 18 Max :120 // age maximum 120}, city:{type:'String', enum:[' Beijing ',' Shanghai '] // Other :{type:'String', Validate :[validator,err] // Validator is a validation function,err is an error message}});Copy the code
2.2 verification failed
If the validation fails, an ERR message is returned. Err is an object with the following attributes
Err.errors // Error set (object) err.errors.color // Error attribute (Schema color attribute) err.errors.color.message // Error attribute information err.errors.path // Error attribute path Type // Error type err.name // Error name err.message // Error messageCopy the code
If validation fails, both Model and Entity will have the same errors attribute as ERR
3 – configuration items
When using new Schema(config), we can append a parameter, options, to configure the Schema configuration. For example:
const ExampleSchema = new Schema(config,options)
// or
const ExampleSchema = new Schema(config)
ExampleSchema.set(option,value)
Copy the code
Options:
- autoIndex: bool – defaults to null (which means use the connection’s autoIndex option)
- bufferCommands: bool – defaults to true
- capped: bool – defaults to false
- collection: string no default
- id: bool defaults to true
- _id: bool defaults to true
- minimize: bool controls document#toObject behavior when called manually defaults to true
- read: string
- safe: bool defaults to true.
- shardKey: bool defaults to null
- strict: bool defaults to true
- toJSON object no default
- toObject object no default
- typeKey string defaults to ‘type’
- useNestedStrict boolean defaults to false
- validateBeforeSave bool defaults to true
- versionKey: string defaults to “__v”
- collation: object defaults to null (which means use no collation)
See official documentation for details.
3.1– safe — Security attributes (default security)
Generally, you can perform the following configurations:
new Schema({... },{safe:true})Copy the code
And of course we can
new Schema({... },{safe:{j:1,w:2,wtimeout:10000}})Copy the code
J indicates to make one log, w indicates to make two copies (not clear), and the timeout is 10 seconds
3.2– strict — Strict configuration (enabled by default)
The default value is Enabled, which ensures that the Entity value is automatically validated before being stored in the database. If the field in the instance does not exist in the schema, the field will not be inserted into the database. If you do not have a good reason, please do not use it. Examples:
const ThingSchema = new Schema({a:String})
const ThingModel = db.model('Thing',SchemaSchema)
const thing = new ThingModel({iAmNotInTheThingSchema:true})
thing.save() // iAmNotInTheThingSchema will not be saved
Copy the code
If the strict option is disabled, iAMnot the ThingSchema will be saved to the database
This option can also be used when constructing instances, for example:
const ThingModel = db.model('Thing')
const thing1 = new ThingModel(doc,true) // open
const thing2 = new ThingModel(doc,false) // close
Copy the code
Note: Strict can also be set to throw to indicate that an error will be thrown if a problem occurs
3.3– capped — Capped
If there are batch operations on the database, this property can limit the number of operations at a time, for example:
new Schema({... },{capped:1024}) // can operate 1024 at most onceCopy the code
Of course, this parameter can also be a JSON object, containing the size, Max, and autiIndexId attributes
new Schema({... },{capped:{size:1024,max:100,autoIndexId:true}})Copy the code
3.4 – versionKey – lock
Version lock is Mongoose default configuration (__v attribute), if you want to customize, as follows:
new Schema({... },{versionKey:'__someElse'});Copy the code
Instead of a __v attribute, the version lock stored in the database is __someElse, which gives the version lock a name. It’s up to Mongoose and MongoDB to decide how to store it, but you can also remove this attribute.
new Schema({... },{versionKey:false});Copy the code
Unless you know what you’re doing, and you know the consequences
3.5– autoIndex — Automatic index
To start the application, Mongoose sends an ensureIndex command for each index. Index default (_id) is created by Mongoose.
We can set this option when we do not need to set the index.
const schema = new Schema({.. }, { autoIndex: false }) const Clock = mongoose.model(‘Clock’, schema) Clock.ensureIndexes(callback)
4 – the expansion of the Schema
4.1 Instance Method
Sometimes we create schemas that not only provide common properties but also common methods for subsequent models and entities.
The following example is more advanced than the fast channel example and can be extended at a higher level:
const schema = new Schema({ name: String, type: String}) / / check similar data schema. The methods. FindSimilarTypes = () = > {return mongoose. Model (' Oinstance). Find ({type: 'engineer' }) } const Oinstance = mongoose.model('Oinstance', schema) module.exports = OinstanceCopy the code
Use as follows:
const Oinstance = require('.. /models/06instance-method') const m = new Oinstance try { let res = await m.findSimilarTypes() ctx.body = res } catch (e) { console.log('! err==', e) return next }Copy the code
4.2 Static Methods
Static methods can be used in the Model layer as follows:
const schema = new Schema({ name: String, type: String }) schema.statics.findSimilarTypes = () => { return mongoose.model('Ostatic').find({ type: // example const Ostatic = mongoose. Model ('Ostatic', schema) module.exports = OstaticCopy the code
Try {let res = await ostatic.findsimilartypes () ctx.body = res} catch (e) {console.log(‘! err==’, e) return next }
The difference between methods and statics
The difference is that one adds statics to models and one adds methods to instances.
4.3 Virtual Properties
If a virtual attribute is defined in the Schema, the attribute will not be written to the database. For example:
const PersonSchema = new Schema({
name:{
first:String,
last:String
}
})
const PersonModel = mongoose.model('Person',PersonSchema)
const daly = new PersonModel({
name:{first:'daly',last:'yang'}
})
Copy the code
If that’s what happens every time you want to use your full name
console.log(daly.name.first + ' ' + daly.name.last);
Copy the code
Obviously this is cumbersome, we can define virtual properties:
PersonSchema.virtual('name.full').get(function(){
return this.name.first + ' ' + this.name.last;
});
Copy the code
Then you can call the full name with daly.name.full, and inversely solve the first and last attributes if you know full
PersonSchema.virtual('name.full').set(function(name){
var split = name.split(' ');
this.name.first = split[0];
this.name.last = split[1];
});
var PersonModel = mongoose.model('Person',PersonSchema);
var krouky = new PersonModel({});
krouky.name.full = 'daly yang';
console.log(krouky.name.first);
Copy the code
Model
1– What is Model
Model is constructed by Schema. In addition to the database skeleton defined by Schema, it also has a database behavior Model, which is equivalent to the class that manages database properties and behaviors.
In fact, the Model is the most straightforward part of manipulating the database. All of our CRUDS revolve around the Model.
2– How to create a Model
You must create it with Schema as follows:
const TankSchema = new Schema({
name:'String',
size:'String'
})
const TankModel = mongoose.model('Tank',TankSchema)
Copy the code
3 – operation Model
This model can be used directly to manipulate the API, for example:
const tank = {'something',size:'small'}
TankModel.create(tank)
Copy the code
Note:
You can use Model to create an Entity. The Entity is a specific Model concrete object, but it does not have Model methods, so it has to use its own methods.
const tankEntity = new TankModel('someother','size:big');
tankEntity.save()
Copy the code
The instance
increase
- save()
- create()
- InsertOne () inserts a single piece of data
- InsertMany () is faster than create because it operates on multiple pieces of data at once
Use the save method for Entity and create method for Model
module.exports = { async mCreateModal(ctx, next) { let result = { success: false, code: 0, resultDes: ""} let param = ctx.request.body try {// Modal create let data = await ocrud. create(param) result.success = true result.data = data ctx.body = result } catch (e) { console.log('! err==', e) result.code = -1 result.resultDes = e ctx.body = result return next } }, async mCreateEntity(ctx, next) { let result = { success: false, code: 0, resultDes: ""} let param = ctx.request.body const user = new Ocrud(param) try {// Entity create let data = await user.save() result.success = true result.data = data ctx.body = result } catch (e) { console.log('! err==', e) result.code = -2 result.resultDes = e ctx.body = result return next } }, async mInsertMany(ctx, next) { let result = { success: false, code: 0, resultDes: "" } let param = ctx.request.users try { let data = await user.insertMany(param) result.success = true result.data = data ctx.body = result } catch (e) { console.log('! err==', e) result.code = -2 result.resultDes = e ctx.body = result return next } }, }Copy the code
update
There are three ways to update data:
- Update This method matches the content it is looking for and does not return data
- Updateone updates one at a time
- UpdateMany Updates multiple entries at a time
- FindOneAndUpdate this method updates the database based on the lookup and also returns the unchanged data found
- FindByIdAndUpdate This method does the same as the findOneAndUpdate method above, except that it finds and updates documents by ID
Each of the three methods takes four parameters. To illustrate the meaning of several parameters:
Model.update(conditions, doc, [options], [callback])
Copy the code
Conditions: update data object, which is an object containing key-value pairs. Options: an option to declare the type of operation, which is described in more detail below
options
Safe (Boolean) : Defaults to true. Safe mode upsert (Boolean) : Default is false. Create a new record if it does not exist multi (Boolean) : Default is false. SetDefaultsOnInsert: If the upsert option is true, insert the document definition default value strict (Boolean) on new: Overwrite (Boolean) : Default is false. Disable update-only mode to allow records to be overwrittenCopy the code
The options parameter is set differently in the update method than in findOneAndUpdate or findByIdAndUpdate.
In the update method, the options option is set to:
{safe: true | false, / / whether the statement returns an error message, the default true upsert: false | true, / / statement if the query is less than the data items need to be updated, whether to need new insert a record, Default false multi: false | true, / / declare whether can update of multiple records at the same time, the default false strict: true / / | false statement can update data included in the field data schema definition, the default true}Copy the code
FindOneAndUpdate,options Options are as follows:
New: bool - The default is false. Returns the modified data. Upsert: bool - Default false. If it does not exist, create a record. Fields: {Object | String} - select field. Similar. Select (fields). FindOneAndUpdate (). MaxTimeMS: Query time upper limit. Sort: If there are more than one query criteria, the query is updated in order. RunValidators: If the value is true, perform Validation. SetDefaultsOnInsert: If the upsert option is true, insert the default values defined by the document on new creation. RawResult: If true, the original result is taken as the third argument to the callback function.Copy the code
FindByIdAndUpdate, options Options are as follows:
New: bool - The default is false. Returns the modified data. Upsert: bool - Default false. If it does not exist, create a record. RunValidators: If the value is true, perform Validation. SetDefaultsOnInsert: If the upsert option is true, insert the default values defined by the document on new creation. Sort: If there are more than one query criteria, the query is updated in order. Select: Sets the returned data field rawResult: Returns the original result if trueCopy the code
Example:
// START async mUpdate(ctx, next) { let result = { success: false, code: 0, resultDes: "" } let condition = ctx.request.body.condition let doc = ctx.request.body.doc console.log(condition, '===condition') console.log(doc, '===doc') try { let data = await Ocrud.update(condition, doc, { multi: true }) result.success = true result.data = data ctx.body = result } catch (e) { console.log('! er==', e) result.code = -3 result.resultDes = e ctx.body = result return next } }, async mUpdateOne(ctx, next) { let result = { success: false, code: 0, resultDes: "" } let condition = ctx.request.body.condition let doc = ctx.request.body.doc try { let data = await Ocrud.updateOne(condition, doc) result.success = true result.data = data ctx.body = result } catch (e) { console.log('! er==', e) result.code = -3 result.resultDes = e ctx.body = result return next } }, async mUpdateMany(ctx, next) { let result = { success: false, code: 0, resultDes: "" } let condition = ctx.request.body.condition let doc = ctx.request.body.doc try { let data = await Ocrud.updateMany(condition, doc, { multi: true }) result.success = true result.data = data ctx.body = result } catch (e) { console.log('! er==', e) result.code = -3 result.resultDes = e ctx.body = result return next } }, async mFindOneAndUpdate(ctx, next) { let result = { success: false, code: 0, resultDes: "" } let condition = ctx.request.body.condition let doc = ctx.request.body.doc try { let data = await Ocrud.findOneAndUpdate(condition, doc, { new: true, rawResult: true }) result.success = true result.data = data ctx.body = result } catch (e) { console.log('! er==', e) result.code = -3 result.resultDes = e ctx.body = result return next } }, async mFindByIdAndUpdate(ctx, next) { let result = { success: false, code: 0, resultDes: "" } let _id = ctx.request.body.id let doc = ctx.request.body.doc try { let data = await Ocrud.findByIdAndUpdate(_id, doc) result.success = true result.data = data ctx.body = result } catch (e) { console.log('! er==', e) result.code = -3 result.resultDes = e ctx.body = result return next } }, // ENDCopy the code
delete
- Remove () removes all documents that meet the criteria. If you want to delete only the first qualifying object, add the setting single to true
- Delete () removes the first document that matches the object, ignoring the value of single
- DeleteMany () deletes all qualified documents, ignoring the value of single
- findOneAndRemove()
- findByIdAndRemove()
The remove method can be used in two ways, one on models and the other on model instances, for example:
User.remove({ name : /Simon/ } , function (err){ if (! Err){// Delete all users whose names include Simon}}); User.findOne({ email : '[email protected]'},function (err,user){ if (! Err){user.remove(function(err){// delete the first user matching the mailbox})}})Copy the code
MaxTimeMS: requires mongodb >= 2.6.0 select: requires mongodb >= 2.6.0 select: Set the returned data field rawResult: returns the rawResult if true
User.findOneAndRemove({name : /Simon/},{sort : 'lastLogin', select : 'name email'},function (err, user){ if (! err) { console.log(user.name + " removed"); // Simon Holmes removed } })Copy the code
The other findByIdAndRemove method is exactly the same. Select: sets the returned data field. RawResult: Returns the original result if it is true
User.findByIdAndRemove(req.body._id,function (err, user) {
if(err){
console.log(err)
return
}
console.log("User deleted:", user)
})
Copy the code
Example:
// START async mDelete(ctx, next) { let result = { success: false, code: 0, resultDes: "" } let param = ctx.request.body.condition try { let data = await Ocrud.delete(param) result.success = true result.data = data ctx.body = result } catch (e) { console.log('! er==', e) result.code = -3 result.resultDes = e ctx.body = result return next } }, async mRemove(ctx, next) { let result = { success: false, code: 0, resultDes: "" } let param = ctx.request.body.condition try { let data = await Ocrud.remove(param) result.success = true result.data = data ctx.body = result } catch (e) { console.log('! er==', e) result.code = -3 result.resultDes = e ctx.body = result return next } }, async mDeleteMany(ctx, next) { let result = { success: false, code: 0, resultDes: "" } let param = ctx.request.body.condition try { let data = await Ocrud.deleteMany(param) result.success = true result.data = data ctx.body = result } catch (e) { console.log('! er==', e) result.code = -3 result.resultDes = e ctx.body = result return next } }, async mFindOneAndRemove(ctx, next) { let result = { success: false, code: 0, resultDes: "" } let param = ctx.request.body.condition try { let data = await Ocrud.findOneAndRemove(param) result.success = true result.data = data ctx.body = result } catch (e) { console.log('! er==', e) result.code = -3 result.resultDes = e ctx.body = result return next } }, async mFindByIdAndRemove(ctx, next) { let result = { success: false, code: 0, resultDes: "" } let param = ctx.request.body.id try { let data = await Ocrud.findByIdAndRemove(param) result.success = true result.data = data ctx.body = result } catch (e) { console.log('! er==', e) result.code = -3 result.resultDes = e ctx.body = result return next } }, // ENDCopy the code
Integrated writing
- BulkWrite () can send insertOne, updateOne, updateMany, replaceOne, deleteOne, and/or deleteMany at a time, which is more efficient than sending a single command at a time
Character.bulkWrite([
{
insertOne: {
document: {
name: 'Eddard Stark',
title: 'Warden of the North'
}
}
},
{
updateOne: {
filter: { name: 'Eddard Stark' },
// If you were using the MongoDB driver directly, you'd need to do
// `update: { $set: { title: ... } }` but mongoose adds $set for
// you.
update: { title: 'Hand of the King' }
}
},
{
deleteOne: {
{
filter: { name: 'Eddard Stark' }
}
}
}
]).then(handleResult)
Copy the code
Query
Query constructors are used to build queries. Instead of instantiating Query directly, MOdel functions like model.find () can be used.
const query = MyModel.find(); // `query` is an instance of `Query`
query.setOptions({ lean : true });
query.collection(model.collection);
query.where('age').gte(21).exec(callback);
// You can instantiate a query directly. There is no need to do
// this unless you're an advanced user with a very good reason to.
const query = new mongoose.Query();
Copy the code
Chain query
Since the query operation always returns itself, we can use a more graphic chained notation
query
.find({ occupation: /host/ })
.where('name.last').equals('Ghost')
.where('age').gt(17).lt(66)
.where('likes').in(['vaporizing', 'talking'])
.limit(10)
.sort('-occupation')
.select('name occupation')
.exec(callback);
Copy the code