The introduction

Following the previous article “Koa2+MongoDB+JWT Practical use –Restful API Best Practice”, I received feedback from many partners that they did not know much about Mongoose, and it was difficult to get started. The official documents were mostly in English (the baby felt bitter, but the baby did not speak).

In order to help you quickly get started and deepen your understanding of Mongoose, I sorted out some basic knowledge about Mongoose based on previous projects, which are very useful for actual combat. I believe that reading this article will be of great help to you to get started quickly and understand how to use Mongoose.

There are many concepts and modules involved in Mongoose, including the following:

This article doesn’t go into detail, but focuses on the modules that are important in the field: Schemas, SchemaTypes, Connections, Models, and tables.

Schemas

Define your schema

Everything at Mongoose started with a Schema. Each schema maps to a collection of MongoDB and defines the form of documents in that collection.

const mongoose = require("mongoose");

const { Schema, model } = mongoose;

const userSchema = new Schema(
  {
    __v: { type: Number.select: false },
    name: { type: String.required: true },
    password: { type: String.required: true.select: false },
    avatar_url: { type: String },
    gender: {
      type: String.enum: ["male"."female"].default: "male".required: true
    },
    headline: { type: String}, {},timestamps: true});module.exports = model("User", userSchema);

Copy the code

Here __v is versionKey. This versionKey is a property created by Mongoose when each document is first created. Contains internal revisions of the document. This document property is configurable. The default value is __v. If the version number is not needed, add {versionKey: false} to the schema.

Create the model

To use our schema definition, we need to convert our userSchema into a model that we can use. So mongoose. Model (Model name, schema). In the code above:

module.exports = model("User", userSchema);
Copy the code

Options

Schemas have several configurable options that can be passed directly to constructors or Settings:

newSchema({.. }, options);// or

var schema = newSchema({.. }); schema.set(option, value);Copy the code

Available options:

  • autoIndex
  • bufferCommands
  • capped
  • collection
  • id
  • _id
  • minimize
  • read
  • shardKey
  • strict
  • toJSON
  • toObject
  • typeKey
  • validateBeforeSave
  • versionKey
  • skipVersioning
  • timestamps

Here I just list the commonly used configuration items, complete the configuration items, https://mongoosejs.com/docs/guide.html#options to view the official document.

I’m going to focus on versionKey and timestamps:

  • versionKey(mentioned above) is automatically set by Mongoose when the file is created. This value contains the internal revision number of the file. VersionKey is a string that represents the attribute name of the version number. The default value is__v
  • If you set it uptimestampsOption, Mongoose will automatically add to your schemacreatedAtupdatedAtField of typeDate.

Now that you’ve basically covered Schema, look at SchemaTypes

SchemaTypes (SchemaTypes)

SchemaTypes select default values for query and other processing path defaults, validation, getters, setters, fields, and special characters for strings and numbers. SchemaTypes in effect in Mongoose are:

  • String
  • Number
  • Date
  • Buffer
  • Boolean
  • Mixed
  • ObjectId
  • Array
  • Decimal128
  • Map

Look at a simple example:

const answerSchema = new Schema(
  {
    __v: { type: Number.select: false },
    content: { type: String.required: true },
    answerer: {
      type: Schema.Types.ObjectId,
      ref: "User".required: true.select: false
    },
    questionId: { type: String.required: true },
    voteCount: { type: Number.required: true.default: 0}}, {timestamps: true});Copy the code

All Schema types

  • required: Boolean value or function that, if true, adds the required validation to this attribute.
  • default: Any type or function that sets a default value for the path. If the value is a function, the return value of the function is used as the default.
  • select: Boolean specifies the default value for queryprojections
  • validate: function to add validation functions to attributes.
  • get: function, usedObject.defineProperty()Define custom getters
  • set: function, usedObject.defineProperty()Define custom setters
  • alias: Is a character stringMongoose > = 4.10.0Effective. Defines a virtual property with a given name that gets/sets the path

The index

You can declare MongoDB’s indexes with the Schema type option.

  • index: Boolean value, whether to define an index in the attribute.
  • unique: Boolean value, whether to define a unique index in the attribute.
  • sparse: Boolean value, whether to define a sparse index in the attribute.
var schema2 = new Schema({
  test: {
    type: String.index: true.unique: true // if 'unique' is specified as true, it is the only index}});Copy the code

string

  • lowercase: Boolean value whether to call this value before savingtoLowerCase()
  • uppercase: Boolean value whether to call this value before savingtoUpperCase()
  • trim: Boolean value whether to call this value before savingtrim()
  • match: re: Creates a validator to verify that the value matches the given regular expression
  • enumCreate a validator that verifies that the value is an element of the given array

digital

  • min: number, creates a validator to verify that the value is greater than or equal to the given minimum value
  • max: number, creates a validator to verify that the value is less than or equal to the given maximum value

The date of

  • min: Date
  • max: Date

Now that Schematype is covered, let’s look at Connections.

Connections

We can connect MongoDB by using the Mongoose.connect () method.

mongoose.connect('mongodb://localhost:27017/myapp');
Copy the code

This is the minimum value (27017) for a connection running in the local MyApp database. If the connection fails, try using 127.0.0.1 instead of localhost.

Of course, you can specify more parameters in the URI:

mongoose.connect('mongodb://username:password@host:port/database? options... ');
Copy the code

Operating the cache

This means that we don’t have to wait for the connection to be established to use models. Mongoose will cache the Model operation first

let TestModel = mongoose.model('Test'.new Schema({ name: String }));
// The operation will be suspended until the connection succeeds
TestModel.findOne(function(error, result) { / *... * / });

setTimeout(function() {
  mongoose.connect('mongodb://localhost/myapp');
}, 60000);
Copy the code

If you want to disable caching, you can modify the bufferCommands configuration or disable bufferCommands globally

mongoose.set('bufferCommands'.false);
Copy the code

options

The connect method also receives an Options object:

mongoose.connect(uri, options);
Copy the code

Here I list a few options that are important in daily use, and see the full connection options here

  • bufferCommands: This is a special option in Mongoose (not passed to the MongoDB driver) that disables Mongoose’sThe buffer mechanism.
  • user/pass: User name and password for authentication. These are special options in Mongoose, and they can be equated to those in MongoDB driversauth.userandauth.passwordOptions.
  • dbName: Specifies which database to connect to and overrides any database in the connection string.
  • useNewUrlParser: Underlying MongoDB has deprecated the current connection string parser. Because this is a major change, the useNewUrlParser tag was added to allow users to return the old parser in a new parser if they encounter a bug.
  • poolSize: Maximum number of sockets that the MongoDB driver will hold for this connection. By default, poolSize is 5.
  • useUnifiedTopology: The default value isfalse. Set to true to select a new connection management engine that uses the MongoDB driver. You should set this option to true, except in rare cases that prevent you from maintaining a stable connection.

Example:

const options = {
  useNewUrlParser: true.useUnifiedTopology: true.autoIndex: false.// Do not create index
  reconnectTries: Number.MAX_VALUE, // Always try to reconnect
  reconnectInterval: 500.// Reconnect every 500ms
  poolSize: 10.Maintain a maximum of 10 socket connections
  Return an error immediately if there is no connection, instead of waiting to reconnect
  bufferMaxEntries: 0.connectTimeoutMS: 10000.// The reconnection will be aborted 10 seconds later
  socketTimeoutMS: 45000.// Close Sockets after 45s inactivity
  family: 4 // Use IPv4, skip IPv6
};
mongoose.connect(uri, options);
Copy the code

The callback

The connect() function also takes a callback argument that returns a Promise.

mongoose.connect(uri, options, function(error) {
  // Check for errors and initialize the connection. The callback has no second argument.
});

// Use promise
mongoose.connect(uri, options).then(
  (a)= > { /** ready to use. The `mongoose.connect()` promise resolves to undefined. */ },
  err => { /** handle initial connection error */});Copy the code

With Connections out of the way, let’s look at a key model

Models

Models is a constructor compiled from Schema. Their instances represent documents that can be saved and read from the database. All of the creation and reading of documents from the database is done through the Model.

const mongoose = require("mongoose");

const { Schema, model } = mongoose;

const answerSchema = new Schema(
  {
    __v: { type: Number.select: false },
    content: { type: String.required: true}, {},timestamps: true});module.exports = model("Answer", answerSchema);

Copy the code

Once you’ve defined your model, you can add, delete, change, and review it

create

For Entity, use the save method; If it’s Model, use the create or insertMany methods.

// save([options], [options.safe], [options.validateBeforeSave], [fn])
let Person = mongoose.model("User", userSchema);
let person1 = new Person({ name: 'forest' });
person1.save()

// To use the save() method, you need to instantiate the document before using the save() method to save the document. The create() method, on the other hand, operates directly on the Model and can add multiple documents simultaneously
// Model.create(doc(s), [callback])
Person.create({ name: 'forest' }, callback)

// Model.insertMany(doc(s), [options], [callback])
Person.insertMany([{ name: 'forest' }, { name: On the morning of ' '}].function(err, docs) {})Copy the code

Here, we first need to supplement the three concepts in Mongoose: Schema, Model and entity:

  • schema: A database model skeleton that is stored as a file and does not have the operation capability of a database
  • model: A model generated by a schema publication, a pair of database operations with abstract properties and behavior
  • entity: An entity created by Model whose actions also affect the database

Remember: Schema creates Model, Model creates Entity. Both Model and Entity can affect database operations, but Model is more operational than Entity.

The query

Finding documentation for Mongoosecha is easy, and it supports rich query MongoDB syntax. Including find, findById, and findOne.

find()

Function (err,docs){} Function (err,docs){} Function (err,docs){}

Model.find(conditions, [projection], [options], [callback])
Copy the code

Let’s take a look at how each of the find() parameters is used in a real-world scenario:

  • conditions

    • Looking for all
    Model.find({})
    Copy the code
    • Accurate search
    Model.find({name:'forest'})
    Copy the code
    • Using operators

    Compare correlation operators

    symbol describe
    $eq Equals the specified value
    $ne Not equal to the specified value
    $gt Greater than the specified value
    $gte Greater than or equal to the specified value
    $lt Less than the specified value
    $lte Less than or equal to the specified value
    $in Matches any of the values specified in the query array
    $nin Does not match any of the values specified in the query array
    Model.find({ age: { $in: [18.24]}})Copy the code

    Return all documents whose age field is equal to 18 or 24.

    Logical correlation operators

    symbol describe
    $and Satisfies all the conditions specified in the array
    $nor All conditions specified in the array are not met
    $or Satisfies one of the conditions specified in the array
    $not Reverses the query to return documents that do not meet the specified criteria
    // Return documents whose age field is greater than 24 or whose age field does not exist
    Model.find( { age: { $not: { $lte: 24 }}})
    Copy the code

    Field correlation operators

    symbol describe
    $exists Matches documents with the specified field
    $type Returns a document whose fields are of the specified type

    Array field lookup

    symbol describe
    $all Matches the array field that contains all the conditions specified in the query array
    $elemMatch Matches a value in an array field that satisfies all the criteria specified in $elemMatch
    $size Matches a document whose array field has the same length as the specified size
    // Use $all to find both 18 and 20 documents
    Model.find({ age: { $all: [ 18.20]}});Copy the code
  • projection

    To specify which Document fields (also known as query “projections”) to include or exclude, you must specify both, not both, except for _id.

    There are two types of specification in Mongoose, string specification and object specification.

    If a string is specified, a – sign is added to the front of the excluded field. If only the field name is included.

    Model.find({},'age');
    Model.find({},'-name');
    Copy the code

    When the object form is specified, 1 is included and 0 is excluded.

    Model.find({}, { age: 1 });
    Model.find({}, { name: 0 });
    Copy the code
  • options

    // Three ways to implement
    Model.find(filter,null,options)
    Model.find(filter).setOptions(options)
    Model.find(filter).<option>(xxx)
    Copy the code

    Options option to see the official document Query. Prototype. SetOptions ().

    Here we only list the common ones:

    • sort: Sort according to the given columns. The values can be ASC, desc, Ascending, DESCENDING, 1, or -1.
    • limit: Specifies the maximum number of results to return
    • skip: Specifies the number of documents to skip
    • lean: returns a normal JS object instead ofMongoose Documents. It is suggested that data returned to the front end without special processing by Mongoose should be converted into ordinary JS objects using this method.
    // sort Specifies sort in two ways
    Model.find().sort('age -name'); // A character string with - means descending
    Model.find().sort({age:'asc'.name:- 1});
    Copy the code

    When sort and limit are used together, the order in which they are called does not matter; the data returned is sorted first and then limited.

    // It has the same effect
    Model.find().limit(2).sort('age');
    Model.find().sort('age').limit(2);
    Copy the code
  • callback

    All queries passed callback in Mongoose are in the form of callback(error, result). If there is an error, error is the error message and result is null. If the query succeeds, error is null and result is the query result. The structure of the query result varies according to the query method.

    The find() method returns an array, even if it does not find the contents.

findById

Model.findById(id,[projection],[options],[callback])
Copy the code

Model.findbyid (id) is equivalent to model.findone ({_id: id}).

Take a look at the official comparison between findOne and findById:

The difference is in the case where the id is undefined. FindOne ({_id: undefined}) is findOne({}) and returns any data. FindById (undefined) equals findOne({_id: null}) and returns NULL.

Query result:

  • The format of the returned data is{}Object form.
  • Id forundefinednullThe result back tonull.
  • Result returns no data that matches the query conditionnull.

findOne

This method returns the first of all instances found

Model.findOne(conditions, [projection], [options], [callback])
Copy the code

If the query condition is _id, findById() is recommended.

Query result:

  • The format of the returned data is{}Object form.
  • If multiple data items meet the search criteria, only the first item is returned.
  • If conditions are {}, NULL or undefined, any data will be returned.
  • If no data match the query condition, result returns null.

update

Each model has its own update method for modifying documents in the database without returning them to your application. Common ones include findOneAndUpdate(), findByIdAndUpdate(), update(), updateMany(), and so on.

findOneAndUpdate()

Model.findOneAndUpdate(filter, update, [options], [callback])
Copy the code
  • filter

    Query statement, same as find().

    If filter is {}, only the first data is updated.

  • update

    {operator: { field: value, ... },... }Copy the code

    The update operator must be used. $set if there is no operator or if the operator is not the update operator (unique to Mongoose)

    Field correlation operators

    symbol describe
    $set Setting field values
    $currentDate Set the field value to the current timeDateOr timestamp format.
    $min Updated only if the specified value is less than the current field value
    $max Updated only if the specified value is greater than the current field value
    $inc Increment the field valueSpecify the number of.Specify the number ofIt could be a negative number. It could be a decrease.
    $mul Multiplies the value of a field by a specified amount
    $unset Deletes the specified field, and the array value is null.

    Array field related operators

    symbol describe
    $ Serves as a placeholder for the first element in an array field that matches the query criteria{operator:{ "arrayField.$" : value }}
    $addToSet Adds a previously nonexistent element to an array field{ $addToSet: {arrayField: value, ... }}, value is an array$eachUse in combination.
    $push Adds an element to the end of an array field{ $push: { arrayField: value, ... }}, value is an array$eachAnd other modifiers
    $pop Removes the first or last element in an array field{ $pop: {arrayField: -1(first) / 1(last), ... }}
    $pull Removes all elements from array fields that match the query criteria{ $pull: {arrayField: value / condition, ... }}
    $pullAll Removes all matching values from the array{ $pullAll: { arrayField: [value1, value2 ... ] . }}

    The modifier

    symbol describe
    $each modified$push$addToSetOperator to add multiple elements to an array field.
    $position modified$pushOperator to specify the position in the array of the element to be added.
    $slice modified$pushOperator to limit the size of the updated array.
    $sort modified$pushOperator to reorder the elements in an array field.

    The order in which modifiers are executed (regardless of the order in which they are defined) :

    • Adds an element at the specified location to update the array field
    • Sort by the specified rule
    • Limit array size
    • The storage arrays
  • options

    • Lean: true returns normal JS objects instead ofMongoose Documents.
    • New: Boolean value,trueReturns the updated data,false(Default) Returns the data before update.
    • Fields/SELECT: Specifies the fields to be returned.
    • Sort: If the query criteria find more than one document, set the sort order to select which document to update.
    • MaxTimeMS: Sets time limits for queries.
    • Upsert: Boolean value to create an object if it does not exist. The default value isfalse.
    • OmitUndefined: Boolean iftrue, the value is deleted before the updateundefinedProperties.
    • RawResult: if yestrue, the native result from MongoDB is returned.
  • callback

    • No data found returnednull
    • Update success returns the data before update ({}Form)
    • options{new:true}, the updated data is returned ({}Form)
    • There is no query condition, that isfilterIf the value is null, the first data is updated

findByIdAndUpdate()

Model.findByIdAndUpdate(id, update, options, callback)
Copy the code

Model.findbyidandupdate (id, update) is equivalent to model.findoneandUpdate ({_id: id}, update).

Result Query result:

  • The format of the returned data is{}Object form.
  • Id forundefinednullThe result back tonull.
  • Result returns no data that matches the query conditionnull.

update()

Model.update(filter, update, options, callback)
Copy the code
  • options

    • Multi: the defaultfalse, only the first data is updated; fortrue, multiple documents that meet the query conditions will be updated.
    • Overwrite: The default value isfalse, i.e.,updateArguments are added by default if they have no operator or if the operator is not the update operator$set; If it istrueIs not added$setIs considered to overwrite the original document.

updateMany()

Model.updateMany(filter, update, options, callback)
Copy the code

Model. Update (filter, update, {multi: true}, callback)

delete

Common delete methods include findOneAndDelete(), findByIdAndDelete(), deleteMany(), and findByIdAndRemove().

findOneAndDelete()

Model.findOneAndDelete(filter, options, callback)
Copy the code
  • The filter query statement is the same as find()

  • options

    • Sort: If the query criteria finds more than one document, the sort order is set to select which document to delete.
    • Select /projection: Specifies the fields to return.
    • RawResult: if yestrue, returns fromMongoDBNative results of.
  • callback

    • Those who are not eligible tofilterWhen the data is returnednull.
    • filterIs empty or{}, delete the first data.
    • Deleted successfully{}Form of raw data.

findByIdAndDelete()

Model.findByIdAndDelete(id, options, callback)
Copy the code

Model.findbyidanddelete (id) is equivalent to model.findoneAndDelete ({_id: id}).

  • callback
    • Those who are not eligible toidWhen the data is returnednull.
    • idIs empty orundefinedWhen to return tonull.
    • Deleted successfully{}Form of raw data.

deleteMany()

Model.deleteMany(filter, options, callback)
Copy the code
  • filterDelete all matchesfilterConditional documentation.

deleteOne()

Model.deleteOne(filter, options, callback)
Copy the code
  • filterRemove in accordance withfilterThe first document of the condition.

findOneAndRemove()

Model.findOneAndRemove(filter, options, callback)
Copy the code

The usage is the same as findOneAndDelete(), with the minor difference that findOneAndRemove() calls the native findAndModify() command of MongoDB instead of findOneAndDelete().

The findOneAndDelete() method is recommended.

findByIdAndRemove()

Model.findByIdAndRemove(id, options, callback)
Copy the code

Model.findbyidandremove (id) is equivalent to model.findoneAndRemove ({_id: id}).

remove()

Model.remove(filter, options, callback)
Copy the code

Removes all documents that match the Filter condition from the collection. To delete the first document that matches the criteria, set the single option to true.

Ok, after the Models, let’s look at Populate in the field

Populate

Mongoose’s POPULATE () is capable of linking queries to tables, referring to its documents in another collection.

Populate() automatically replaces the specified fields in the Document with replacements retrieved from other collections.

refs

When you create a Model, you can set the REF option to the fields in the Model that are associated with storing the _id of other collections. The REF option tells Mongoose which Model to use when using populate().

const mongoose = require("mongoose");

const { Schema, model } = mongoose;

const answerSchema = new Schema(
  {
    __v: { type: Number.select: false },
    content: { type: String.required: true },
    answerer: {
      type: Schema.Types.ObjectId,
      ref: "User".required: true.select: false
    },
    questionId: { type: String.required: true },
    voteCount: { type: Number.required: true.default: 0}}, {timestamps: true});module.exports = model("Answer", answerSchema);


Copy the code

In the preceding example, set the answerer field of the Answer Model to the ObjectId array. The REF option tells Mongoose to use the User Model when filling. Any _id stored in answerer must be the _id of the Document in the User Model.

ObjectId, Number, String, and Buffer can all be used as refs. However, it is best to use ObjectId.

When creating the document, save the refs field as if it were a normal property and assign the _id value to it.

const Answer = require(".. /models/answers");

async create(ctx) {
  ctx.verifyParams({
    content: { type: "string".required: true}});const answerer = ctx.state.user._id;
  const { questionId } = ctx.params;
  const answer = await newAnswer({ ... ctx.request.body, answerer, questionId }).save(); ctx.body = answer; }Copy the code

populate(path,select)

Fill in the document

const Answer = require(".. /models/answers");

const answer = await Answer.findById(ctx.params.id)
      .select(selectFields)
      .populate("answerer");
Copy the code

The populated answerer field is no longer the _id, but the specified document. This document is returned from the database by another query.

Return field selection

Populate () with a second argument that returns a field string, like query.prototype.select (), if you populate only a portion of the document fields.

const answer = await Answer.findById(ctx.params.id)
      .select(selectFields)
      .populate("answerer"."name -_id");
Copy the code

Populate multiple fields

const populateStr =
      fields &&
      fields
        .split(";")
        .filter(f= > f)
        .map(f= > {
          if (f === "employments") {
            return "employments.company employments.job";
          }
          if (f === "educations") {
            return "educations.school educations.major";
          }
          return f;
        })
        .join("");
const user = await User.findById(ctx.params.id)
      .select(selectFields)
      .populate(populateStr);
Copy the code

The last

Here is the end of this article, here is mainly combined with my usual project (https://github.com/Jack-cool/rest_node_api) to make a simple summary of the use of Mongoose. Hope can bring help to you!

At the same time, you can pay attention to my public account [Front-end Forest], where I will regularly post some cutting-edge articles related to the big front-end and summarize the actual combat in the daily development process.