From aa488b84d30e91055d000e2efa29a6924863f1b9 Mon Sep 17 00:00:00 2001 From: Ehab-24 Date: Fri, 24 Mar 2023 17:13:05 +0500 Subject: [PATCH] feat - mongoose cheatsheet [in progress] Notes: - Schema - Model - Promises - Aggregation --- databases/mongoose.js | 245 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 databases/mongoose.js diff --git a/databases/mongoose.js b/databases/mongoose.js new file mode 100644 index 0000000..8eaf881 --- /dev/null +++ b/databases/mongoose.js @@ -0,0 +1,245 @@ +/* ***************************************************************** + * Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment. + * Mongoose supports both promises and callbacks. + * ****************************************************************/ + +// Import Mongoose +const mongoose = require("mongoose"); + +/* ***************************************************************** + List of Mongoose Data Types: + String + Number + Date + Buffer + Boolean + Mixed + ObjectId + Array + Decimal128 + Map + Schema + UUID + ******************************************************************/ + +/* ***************************************************************** + * Schema + * A schema is a blueprint for a model. + * It defines the structure of the document, default values, validators, etc. + * ****************************************************************/ + +// Define schema for the a collection +const productSchema = new mongoose.Schema({ + name: { + type: String, + immutable: true, // This field cannot be changed after creation + }, + price: { + type: Number, + min: 0, // Minimum value is 0 + max: 10000, // Maximum value is 10000 + required: true, // Not specifying a required field will result in validation error + }, + category: { + type: String, + enum: ["fruit", "vegetable", "dairy"], // Only these values are allowed for 'category' + }, + tags: [ + // Array of String + { + type: String, + minLength: 2, // Minimum length of a single tag is 2 + maxlength: 10, // Maximum length of a single tag is 10 + }, + ], + createdAt: { + type: Date, + // WRONG! + // default: Date.now(), // This results in the current date being set (as a static value) when the schema is compiled + + // Correct! + default: () => Date.now(), // We instead use a getter function that returns the current date when called + }, +}); + +// Create a model from the schema +const Product = mongoose.model("Product", productSchema); +// We can now use the model to create new documents in the 'products' collection + +/* ***************************************************************** + * Promises + * ****************************************************************/ + +Product.create({ + name: "guava", + price: 10, + category: "fruit", + tags: ["green", "sweet", "fresh"], +}); + +Product.insertMany([ + { + name: "apple", + price: 5, + category: "fruit", + tags: ["red", "sweet"], + }, + { + name: "banana", + price: 3, + category: "fruit", + tags: ["yellow", "sweet"], + }, + { + name: "broccoli", + price: 2, + category: "vegetable", + tags: ["green", "healthy"], + }, + { + name: "milk", + price: 4, + category: "dairy", + tags: ["white", "healthy"], + }, +]); + +Product.find(); // Get documents in 'product' colelction + +Product.find({ name: "guava" }); // Get documents where name = 'guava' + +Product.find({ price: { $gt: 5, $lt: 10 } }); // Get documents where 5 < price < 10 + +Product.find({ name: "banana" }).select({ name: 1, price: 1 }); // Get documents where name = 'banana' and only return name and price + +Product.countDocuments({ category: "vegetables" }); // Get count of documents where category = 'vegetables' + +Product.updateOne({ name: "guava" }, { price: 15 }); // Update the price of the document where name = 'guava' to 15 + +Product.deleteOne({ name: "guava" }); // Delete the document where name = 'guava' + +Product.deleteMany({ category: "fruit" }); // Delete all documents where category = 'fruit' + +/* ***************************************************************** + * Aggregation + * The aggregation pipeline refers to a specific flow of operations that processes, transforms, and returns results. + * In a pipeline, successive operations are informed by the previous result. + * Check out the MongoDB documentation at: https://www.mongodb.com/docs/manual/aggregation/ + * ****************************************************************/ + +// * $match +// The following aggration pipeline returns all products where price > 80 AND price < 120. +let pipeline = [ + { + $match: { + price: { + $gt: 80, + $lt: 120, + }, + }, + }, +]; + +// or you can use: +pipeline = [ + { + $match: { + price: { + $and: [{ $gt: 80 }, { $lt: 120 }], + }, + }, + }, +]; + +// Similarly, you can use $or: +pipeline = [ + { + $match: { + category: { + $or: [{ $eq: "fruit" }, { $eq: "vegetable" }], + }, + }, + }, +]; + +// * $group +// The following aggregation pipeline returns the total number of products created less than a year ago, in each category. +pipeline = [ + { + $match: { + date: { + $gte: new Date(new Date().setFullYear(new Date().getFullYear() - 1)), + }, + }, + }, + { + $group: { + _id: "$category", + count: { $sum: 1 }, + }, + }, +]; + +// * $sort +// The following aggregation pipeline returns top 10 most expensive products. +// Note: The $sort stage must come before $limit. +pipeline = [ + { + $sort: { + price: -1, + }, + }, + { + $limit: 10, + }, +]; + +// * $project +// To get the name and price of all products along with their ObjectId: +pipeline = [ + { + $project: { + name: 1, + price: 1, + }, + }, +]; + +// or you can exclude all other fields: +pipeline = [ + { + $project: { + category: 0, // 0 means exclude + tags: 0, + date: 0, + }, + }, +]; +// Note: You may not use both 0 and 1 in the same projection +// other than _id, which is included by default: +pipeline = [ + { + $project: { + _id: 0, // exclude ObjectId as well + name: 1, + price: 1, + }, + }, +]; + +// * $unwind +// The following aggregation returns a new document for 'guava' for each of its tags. +pipeline = [ + { + $match: { + name: "guava", + }, + }, + { + $unwind: { + path: "$tags", // Name of array field that we want to unwind + includeArrayIndex: "arrayIndex", // This will add a new field 'arrayIndex' to each document + preserveNullAndEmptyArrays: true, // This will return a document for each tag, even if the 'tags' is null or empty + }, + }, +];