Skip to main content

Command Palette

Search for a command to run...

4. MongoDB and mongoose

Updated
10 min read

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

  1. 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;
    
  2. Define Schema

    const userSchema = new mongoose.Schema({
      name: { type: String, required: true },
      age: Number,
      skills: [String],
      createdAt: { type: Date, default: Date.now }
    });
    
  3. Create Model

    const User = mongoose.model('User', userSchema);// "Users" will be the collection in DB
    
  4. CRUD operations

    1. 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 method
      
    2. Read(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)
      
    3. 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)
      
    4. Delete

        // 10. deleteOne()
        await User.deleteOne({ name: "Ayush" });
        // ✅ Deletes the first matching document
      
        // 11. deleteMany()
        await User.deleteMany({ age: { $gt: 60 } });
        // ✅ Deletes all matching documents
      
    5. Other 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 (ref another 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 (like find() 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));
}

More from this blog

Node.JS - Basic to Advance

10 posts