Most Commonly Used operators in Mongoose

It is common practice for full stack developers to build a REST API using Node.js, Express, MongoDB. Mongoose is an ODM(Object Document Mapping) tool for Node. Mongoose provides a straight-forward, schema-based solution to model your application data and make coding processes more efficient.
    In this blog, I will list the most used Mongoose helper functions for CRUD operations. Model Course will be used as an example in most topics.
  • define a collection using a schema
    const mongoose = require('mongoose');
    mongoose
      .connect('mongodb://localhost/testdb', {
        useNewUrlParser: true,
        useUnifiedTopology: true,
        ...
      })
      .then(() => console.log('MongoDB is connected...'))
      .catch((error) => console.error('Could not connect to Mongodb... ', error));
    
    const Schema = mongoose.Schema;
    
    const courseSchema = new Schema({
      name: String,
      author: String,
      tags: [String],
      date: { type: Date, default: Date.now },
      isPublished: Boolean,
      price: Number
    });
    
    // the below code will create a collection named courses in MongoDB.
    const Course = mongoose.model('Course', courseSchema);
    
    // for a simple model, e.g. only one field, no need to create a Schema object in advance
    //const Dog = mongoose.model('Dog',  { name: String });
    
    // a more complicated schema: with customized validator , enum datatype, regular expression validator
    // a validator depends on other field
    // customized validator: synchronous, asynchronous
    const courseSchema = new Schema({
      name: {
        type: String,
        required:true,
        minlength: 5,
        maxlength: 255,
        match: /^[a-z]{2,}/
      },
      author: String,
      category: {
        type:String,
        required: true,
        enum: ['web', "mobile", "network"],
        lowercase:true
      },
      tags: {
        type: Array,
        validate: {
          validator: (v) => {
            const result = v && v.length > 0;
            if(result) return Promise.resolve(true);
            else return Promise.resolve(false);
            }
          },
          message: "A course should at least one tag."
        },
        tags: {
        type: Array,
        validate: {
          isAsync: true,
          validator: function(v, callback) {
            setTimeout(() => {
              // Do some async work
              const result =  v && v.length > 0; // v ≠ null
              callback(result);
            });
          },
          message: 'A course should have at lease one tag. '
        }
      date: { type:Date, default: Date.now },
      isPublished: Boolean,
      price: {
        type: Number,
        required: function() {
          return this.isPublished;
        },
        min: 10,
        max: 200
      }
    });
                    
    create collection /
  • create/read/update/delete operations
    // create
    const course = new Course({
        tags: ['node2','Angula2r'],
        name: 'Node.js AugularJS2-6',
        author: 'Jane',
        isPublished: true,
        price: 25
      });
     await course.save();
     // or course.save().then(() => console.log('course saved'));
    
    // read  a collection by id
    async function getCourse() {
      return await Course.findById({
        _id: mongoose.Types.ObjectId('5f9da935ccb82368841152f2'),
      });
    
      // other helper functions
      // get one document by category field
      // return await Course.findOne({
      // category: 'web',
      // });
    }
    
    // read a collection
    // and, or
    // order by : price , descending
    // output only two fields: name  price
    return await Course
       .find({isPublished:true})
        .or([{ price: {$gte: 12}},
        { name: /.*by.*/}])
        .sort('-price')
        .select('name price');
    
    
    async function getCourse(pagenumber, pagesize) {
      const courses = await Course
        // .find({ author:'JAJA', isPublished: true})
        // .find({ price: {$gte: 10, $lte: 20 }})
        // .find({ price:{ $in: [10, 20, 30]} })
    
        // use regular expression as filter , ignore lowercase or uppercase
        // .find({userName: { $regex: userName, $options: '$i' }})
        // find({$or:[{author:'JAJA'}, {isPublished: true}]})
        // and + or
        // find({"likes": {$gt:50}, $or: [{"by": "Mike"},{"title": "mongo"}]})
        // .find({})
        .find({ name: /^Redu/ })
        .skip((pagenumber - 1) * pagesize)
        .limit(pagesize)
        .sort({ date: 1 })
        .select({ name: 1, tags: -1, date: 1})
      // .count();
      console.log(courses);
    }
    
    
    // delete a collection
    const course = await Course.findByIdAndRemove(id);
    const result = await Course.deleteOne({_id: id })
    const deleteQty=Course.deleteMany({isPublished: true})
    
    // update a collection
    const result = await Course.findByIdAndUpdate(id, {
        $set:{
          author: 'Newton2',
          isPublished:false
        }
      },{new: true});
    
    const result = await Course.update({_id:id}, {
      $set:{
        author: 'Ada Lovelace',
        isPublished:false
      }
    });
    
    Course.findOneAndUpdate({price: 10},
      {name:"ANNA"}, null, function(err,docs){
      if(err) {
        console.log(err);
      }
      else
      {
        console.log("Original Doc: ", docs);
      }
    });
    
                    
  • no foreign key? try populate helper function
    const mongoose = require('mongoose');
    
    mongoose
      .connect('mongodb://localhost/testdb')
      .then(() => console.log('Connected to MongoDB...'))
      .catch((err) => console.error('Could not connect to MongoDB...', err));
    
    const Author = mongoose.model(
      'Author',
      new mongoose.Schema({
        name: String,
        bio: String,
        website: String,
      })
    );
    
    const Course = mongoose.model(
      'Course',
      new mongoose.Schema({
        name: String,
        author: {
          type: mongoose.Schema.Types.ObjectId,
          ref: 'Author',
        },
      })
    );
    
    async function createAuthor(name, bio, website) {
      const author = new Author({
        name,
        bio,
        website,
      });
    
      const result = await author.save();
      console.log(result);
    }
    
    async function createCourse(name, author) {
      const course = new Course({
        name,
        author,
      });
    
      const result = await course.save();
      console.log(result);
    }
    
    async function listCourses() {
      const courses = await Course.find()
        .populate('author', 'name  -_id') // === {name:1, _id:0}. include author.name, not author._id
        .select('name author');
      console.log(courses);
    }
    
    createAuthor('Mosh', 'My bio', 'My Website'); // _id: 5f45ab152ac7a7164dc635d0
    
    createCourse('Node Course', '5f45ab152ac7a7164dc635d0');
    
    listCourses();
    
                    
  • use Fawn package to mock a transaction in a relational database
    try {
    new Fawn.Task()
    .save('rentals', rental)
    .update('movies', { _id: movie._id}, {
      $inc: { numberInStock: -1 }
    })
    .run();
    res.send(rental);
    
    }catch(ex){
      res.status(500).send('Something failed.');
    
    }
    
                    
  • aggregation pipeline
    extract data from multiple collections to make an array
    // important operators
    modelName.aggregate([
    {$match:{...}},
    {$lookup:{...}},
    {$unwind:{...}},
    {$project:{...}},
    {$group:{...}},
    {$sort:{...}}
    ], (err, docs) => {..});
    
    What does this MongoDB operator roughly correspond to in a relational database?
    $match: where
    
    $lookup: join, foreign key
    
    $unwind: no equivalent in a relational database. It is used for flattening an array.
    In MongoDB, the datatype of a field can by an array.
    Using $unwind can flatten one record such as
    {name : 'Mike', subscribed courses: ['angular', 'react','vue']}
    into three records
    {name : 'Mike', subscribed courses: ['angular']}
    {name : 'Mike', subscribed courses: ['react']}
    {name : 'Mike', subscribed courses: ['vue']}.
    It will facilitate data analyse.
    
    $project: pick out the aggregated fields, including create a new field when necessary
    
    $group, $sort are self-explanatory. I skip them.
     
    // real example
    const mongoose = require("mongoose");
    
    mongoose
      .connect("mongodb://localhost/testdb", {
        useNewUrlParser: true,
        useUnifiedTopology: true,
        useFindAndModify: false,
        useCreateIndex: true,
      })
      .then(() => console.log("MongoDB is connected..."))
      .catch((error) => console.error("Could not connect to Mongodb... ", error));
    
    
    // output fields:
    
    // from customers collection
    // name
    // region
    
    // from itineraries collection
    // salesmanId
    // salesmanName
    // customerId
    // latitude
    // longitude
    // visitDate
    // createdDate
    // updatedDate
    
    const Itinerary = mongoose.model(
      "Itinerary",
      new mongoose.Schema({
        name: String,
        customer_id: mongoose.Schema.Types.ObjectId,
        visitDate: {
          type: Date,
          default: Date.now,
        },
        account: {
          type: Number,
          default: 0,
        },
        location: [String],
      })
    );
    
    async function aggregateByNameDate(salesmanName, formDate, toDate) {
      // formDate, toDate, format: yyyyMMdd
      const startYear = Number(formDate.slice(0, 4));
      const startMonth = Number(formDate.slice(4, 6)) - 1; // monIndex = month -1
      const startDay = Number(formDate.slice(6, 8));
      const endYear = Number(toDate.slice(0, 4));
      const endMonth = Number(toDate.slice(4, 6)) - 1;
      const endDay = Number(toDate.slice(6, 8));
    
      const startYYMMDD = new Date(startYear, startMonth, startDay, 0, 0, 0);
      const endYYMMDD = new Date(endYear, endMonth, endDay, 23, 59, 59);
    
      await Itinerary.aggregate(
        [
          {
            $match: {
              name: salesmanName,
              visitDate: { $gte: startYYMMDD, $lte: endYYMMDD },
            },
          },
          {
            $lookup: {
              from: "customers",
              localField: "customer_id",
              foreignField: "_id",
              as: "customers",
            },
          },
          {
            $unwind: {
              path: "$customers",
              preserveNullAndEmptyArrays: true,
            },
          },
          {
            $project: {
              _id: 1,
              name: 1,
              customer_id: 1,
              "customers.name": 1,
              "customers.region": 1,
              visitDate: 1,
              account: 1,
            },
          },
          {
            $group: {
              _id: {
                salesmanName: "$name",
                customerId: "$customer_id",
                customerName: "$customers.name",
                customerRegin: "$customers.region",
                month: { $month: "$visitDate" },
                year: { $year: "$visitDate" },
              },
              total: { $sum: "$account" },
              count: { $sum: 1 },
            },
          },
          {
            $sort: {
              _id: 1,
            },
          },
        ],
        (err, docs) => {
          if (err) {
            console.log("err is ", err);
            return;
          }
          console.log(docs);
        }
      );
    }
                    
  • add a new field to an existing collection
    1. Add the new field to schema
      const courseSchema = new Schema({
        name: String,
        author: String,
        tags: [String],
        date: { type: Date, default: Date.now },
        isPublished: Boolean,
        price: Number,
        newField: String,
      });
                          
    2. use updateMany helper to update the documents
      return await Course.updateMany(
          { price: { $gte: 1 } },
          { newField: 'OK' },
          { multi: true },
          function (err, numberAffected) {
            console.log(
              'numberAffected error is',
              numberAffected,
              ' are affected ',
              JSON.stringify(err)
            );
          }
        );
                          
  • a special MongoDB datatype : ObjectId
    const mongoose = require('mongoose');
    
    const id = new mongoose.Types.ObjectId();
    console.log(id.getTimestamp());
    console.log('1234 is a valid ObjectId ?  ', mongoose.Types.ObjectId.isValid('1234'));  // result: false
                      
next article>>

Table of Contents