4. MongoDB and mongoose
MongoDB – The Database
MongoDB is a NoSql Database that stores data in the form of documents and in these documents the data is written in KEY - Value pairs .
Unlike SQL databases (tables, rows, columns), MongoDB uses:
Database → container of collections
Collection → container of documents (like a table)
Document → actual data stored as JSON-like objects
{ "_id": "64ef9fabc123", // unique key "name": "Ayush", "age": 22, "skills": ["Node.js", "React", "MongoDB"] }
SQL vs NoSQL
| Aspects | NoSql | SQL |
|---|---|---|
| Data is stores in documents , key-value pairs | Data stored in tables | |
| Scalabilty | Designed to scale horizontaly by adding more servers, distributed the load accross multiple nodes. | |
| (pay as you go model) | Desgined to scale veritically by adding more resources to a single server. | |
| Schema flexibilty | Schema less design, | |
| ex: we can add address field only in that document in which it required it can be optional for other documents. | Predefined schema (table already bana chuke hote hain) | |
| ex: must add address field(cols) | ||
| Performance | Read -write operation are fast | Read-write operation are slow due to ACID transcation overhead |
| Handling Unstructured data | Efficiently handled unstructured data | Best suit for strcutured data (like in well defined tables) |
| Used in | Best fit for -> Social media platforms, Real time applications like chat applications. | Banking SYstems , E-commerce , ERP |
Mongoose - ODM(Object data modelling)
Mongoose is a Node.js library that provides a schema-based solution for MongoDB.
Think of it as a bridge between your Node.js code and MongoDB.
Mongoose WorkFlow
Connect To MongoDB
const mongoose = require('mongoose'); require('dotenv').config(); const dbConnect = ()=>{ mongoose.connect(process.env.DATABASE_URL) .then(()=>{console.log("DB CONNECTION SUCESSFULL")}) .catch((err)=>{ console.log("Failed to connect DB") console.log(err); process.exit(1); }) } module.exports = dbConnect;Define Schema
const userSchema = new mongoose.Schema({ name: { type: String, required: true }, age: Number, skills: [String], createdAt: { type: Date, default: Date.now } });Create Model
const User = mongoose.model('User', userSchema);// "Users" will be the collection in DBCRUD operations
Create(POST Data)
// Assuming Schema + Model already defined const User = mongoose.model("User", userSchema); // ---------------- CREATE ---------------- // Save a new user const newUser = await User.create({ name:"Ayush", age:5, skills:["java","Py"] }) // Saves into DB also use save methodRead(GET Data)
// ---------------- READ ---------------- // 1. find() const allUsers = await User.find({}); // ✅ Returns an array of all the documents(USers) // Use when you expect multiple documents const getUsersWhoseAgeisFive = await User.find({age:5}) // if we use findOne here then it give first matching document const getSelectedFields = await User.find().select('name email -_id') // it give all documenst having only name and email and not include _id const getLimitedUser = await User.find().limit(5).skip(1) // this give only 5 Users exxcept first user -> used for pagination const sortedUser = await User.find().sort({age:1}) // return/find user in acseding order of thier age . if you pass -1 then you get the user in decending order // 2. findOne() const oneUser = await User.findOne({ name: "Ayush" }); // ✅ Returns the first matching document (or null if none) // Use when you need just ONE document // 3. findById() const userById = await User.findById(newUser._id); //newUser._id // ✅ Finds a document by _id directly // Faster than findOne({ _id: ... }) // 4. findByIdAndUpdate() const updatedUser = await User.findByIdAndUpdate( newUser._id, { $set: { age: 23 } }, { new: true } // return the updated document instead of old one ); // ✅ Finds document by _id and updates it in one step // 5. findByIdAndDelete() await User.findByIdAndDelete(newUser._id); // ✅ Deletes document directly using _id // 6. findByIdAndRemove() await User.findByIdAndRemove("64ef9fabc123"); // ❌ Deprecated (use findByIdAndDelete instead)Update
// ---------------- UPDATE ---------------- // 7. updateOne() await User.updateOne({ name: "Ayush" }, { $set: { age: 25 } }); // ✅ Updates the FIRST matching document // 8. updateMany() await User.updateMany({ age: { \(lt: 18 } }, { \)set: { isMinor: true } }); // ✅ Updates ALL matching documents // 9. replaceOne() await User.replaceOne({ name: "Ayush" }, { name: "Raj", age: 30 }); // ✅ Replaces entire document (removes old fields not mentioned)Delete
// 10. deleteOne() await User.deleteOne({ name: "Ayush" }); // ✅ Deletes the first matching document // 11. deleteMany() await User.deleteMany({ age: { $gt: 60 } }); // ✅ Deletes all matching documentsOther queries
// ---------------- EXTRA QUERY HELPERS ---------------- // 12. countDocuments() const count = await User.countDocuments({ age: { $gte: 18 } }); // ✅ Count matching documents (faster than .find().length) // 13. distinct() const uniqueSkills = await User.distinct("skills"); // ✅ Returns unique values for a given field // 14. exists() const exists = await User.exists({ name: "Ayush" }); // ✅ Returns _id if document exists, null if not // 15. sort() const sorted = await User.find().sort({ age: -1 }); // ✅ Sort results (1 = asc, -1 = desc) // 16. limit() const limited = await User.find().limit(5); // ✅ Limit number of documents returned // 17. skip() const skipped = await User.find().skip(5).limit(5); // ✅ Pagination (skip first 5, then get next 5) // 18. select() const selectedFields = await User.find().select("name age"); // ✅ Return only specific fields (projection) // 19. aggregate() const aggregation = await User.aggregate([ { \(match: { age: { \)gte: 18 } } }, { \(group: { _id: null, avgAge: { \)avg: "$age" } } } ]); // ✅ Powerful data aggregation pipeline // 20. populate() // Example: if a post has author ref const posts = await Post.find().populate("author"); // ✅ Fetches related document (like JOIN in SQL) })();
Operators in MongoDB
Schema Given :
import mongoose from "mongoose";
const orderSchema = new mongoose.Schema({
customerName: String,
age: Number,
status: {
type: String,
enum: ["pending", "shipped", "delivered"]
},
totalAmount: Number,
items: [String],
isPaid: Boolean,
orderDate: Date
});
export const Order = mongoose.model("Order", orderSchema); // collection name: orders
//1. Comaprison
// \(gt (greater than) and \)lt(lesser than)
Q: Find orders where totalAmount > 1000
A: Order.find({totalAmount:{$gt:1000}})////db.orders.find -> if not use mongoose
// \(gte (greater than and equal to) , \)lte(lower than and equal to)
Q: Find users aged between 20 and 25
A: Order.find({age: {\(gte:20 , \)lte:25} }) // range based
//2. Array Match
Q: Find orders that include "mobile"
A: Order.find({items:"mobile"}) // like $eq
// \(in and \)nin(not in)
Q: Find orders with items in ["mobile", "laptop"]
A: Order.find({items: {$in:["mobile","Desktop"]} }) // find items in array
//$size
Q:Find orders with exactly 2 items
A: const orders = await Order.find({ items: { $size: 2 } });
//3. Logical operators
// $and
Q: Find delivered orders with amount > 1000
A: Order.find({
$and:[
{status:"deliverd"},
{totalamount: {$gt:1000} }
]
}); // here if we cant write and it automatically detect and too (implicit AND here)
// $or
Q: Find orders that are pending OR shipped
A: Order.find({
$or:[
{status:"pending"},
{status:"shipped"},
]
});
//$not -> check for not condition
//4. Element Operator:
// $exists -> doc meh koi particular field present hai ya nhi
Q: Find documents where age exists
A: Order.find({age: {exists:true} })
// $type -> kisi doc meh koi value ke type ke basis pe
Advance mongoose features
Middleware/Hooks → run before/after operations (
pre,post).Virtuals → computed fields not stored in DB.
Population → like SQL joins (
refanother model).Indexes → schema-level indexing for performance.
Transactions → multi-document atomic operations.
const postSchema = new mongoose.Schema({ title: String, author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } }); const Post = mongoose.model('Post', postSchema); const post = await Post.find().populate('author');
Aggregation Pipeline in MongoDB
The aggregation pipeline is a framework in MongoDB that processes documents in stages. Each stage transforms the documents and passes them to the next stage (similar to a data pipeline).
Common stages:
$match→ filter documents (likefind()but in aggregation).\(group→ group documents by a field and apply accumulators (\)sum,$avg, etc.).$sort→ sort results.$project→ shape the output (include/exclude fields).$lookup→ join data from another collection.\(limit/\)skip→ pagination.const User = require('../models/User') exports.Aggregation = async(req,res)=>{ try{ const Product = [ // this is our model(let suppose) { name:"hella", inStock:true, price:100, category:"Electronics" }, { name:"hella", inStock:false, price:500, category:"Books" }, ] const result = await Product.aggregate([ // Stage 1: filter { $match:{ // macth and get all document that is inStock inStock:true, price:{ // aagain get those instock document whose price in grater than 100 $gte:100 } } }, //Stage 2. Group documents { $group:{ // We group above filtred documents -> group elemtn on the basis of category give all its avg price and count of all document that grouped _id:"$category", // agar same category arrhi hai toh unka ek group ban jaayga avgPrice:{ \(avg:"\)price" }, count:{ $sum:1 }, maxPrice:{ \(max:"\)price" }, minPrice:{ \(min:"\)price" } } } ]) return res.status(200).json({ sucess:true, message:"Data Fetched", data:result }) } catch(err){ } }//example given [ { "_id": 1, "customer": "Ayush", "city": "Prayagraj", "items": [ { "product": "Laptop", "price": 50000, "quantity": 1 }, { "product": "Mouse", "price": 500, "quantity": 2 } ], "status": "delivered", "orderDate": "2026-04-01" }, { "_id": 2, "customer": "Ravi", "city": "Delhi", "items": [ { "product": "Phone", "price": 20000, "quantity": 1 } ], "status": "pending", "orderDate": "2026-04-02" }, { "_id": 3, "customer": "Ayush", "city": "Prayagraj", "items": [ { "product": "Keyboard", "price": 1500, "quantity": 1 } ], "status": "delivered", "orderDate": "2026-04-03" } ] //Find total spending of each customer (only delivered orders), sorted by highest spending db.orders.aggregate([ { $match: { status: "delivered" } },// Only delivered orders (remove pending) { \(unwind: "\)items" },//Each item becomes a separate document: before: items: [ {Laptop}, {Mouse} ] , after: {laptop} , {mouse} { $project: {// Creates new field:total = price × quantity customer: 1, total: { \(multiply: ["\)items.price", "$items.quantity"] } }}, { $group: { _id: "$customer", totalSpent: { \(sum: "\)total" } }},// Group + aggregate: { "_id": "Ayush", "totalSpent": 51500 } { $sort: { totalSpent: -1 } }// Highest spender first ])
Use of “ref”
MongoDB itself is schema-less, but when we use Mongoose (ODM), we define schemas.
In MongoDB, documents don’t have direct joins like SQL tables.
ref is a special option in schema to tell Mongoose which model this ObjectId belongs to. It works like a "pointer" to another collection.
When we call .populate("fieldName"), Mongoose uses that ref to replace the ObjectId with the actual referenced document.
const mongoose = require("mongoose");
// User Schema
const userSchema = new mongoose.Schema({
name: String,
email: String
});
const User = mongoose.model("User", userSchema);
// Order Schema with reference
const orderSchema = new mongoose.Schema({
product: String,
price: Number,
customer: {
type: mongoose.Schema.Types.ObjectId, // store ObjectId
ref: "User" // tells Mongoose this refers to "User" model
}
});
const Order = mongoose.model("Order", orderSchema);
// first create then get using populate
async function getOrders() {
const orders = await Order.find().populate("customer"); // if we do not use populate then it only give id
console.log(JSON.stringify(orders, null, 2));
}