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/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
-
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, });
-
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) ); } );
-
Add the new field to schema
-
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