Chapter 3.3☕ 18 min read

CRUD Operations — Mongoose Se Data Likho-Padho

CRUD is the foundation of all database operations. Mongoose makes each operation clean and async-friendly.

01What is CRUD? — Biryani Ka Poora Jeevan Chakra

CRUD stands for Create, Read, Update, Delete — the four basic operations you can perform on any data. Think of it like the lifecycle of a Biryani order on Swiggy:

  • Create = Customer places a new order (new biryani document is created)
  • Read = Customer checks order status (query the database)
  • Update = Swiggy updates status from "confirmed" to "out for delivery"
  • Delete = Customer cancels the order (document is deleted)

In Mongoose, these operations map to simple async methods:

OperationHTTP MethodMongoose MethodSQL Equivalent
CreatePOSTModel.create(data)INSERT INTO
Read (all)GETModel.find(filter)SELECT * FROM
Read (one)GET /:idModel.findById(id)SELECT * WHERE id=
UpdatePUT /:idModel.findByIdAndUpdate(id, data)UPDATE SET
DeleteDELETE /:idModel.findByIdAndDelete(id)DELETE FROM

Every Mongoose method returns a Promise — so you must use async/await (or .then()) to handle the result. This is because database operations take time (network latency, disk I/O), just like Swiggy delivery takes time!

02Create — Model.create() se Naya Biryani Add Karna

Create is how you add new documents to your MongoDB collection. In Mongoose, the simplest way is Model.create().

// routes/biryani.routes.js — POST route to create a Biryani
import express from 'express';
import Biryani from '../models/Biryani.js';

const router = express.Router();

// POST /api/biryani — Naya biryani add karo
router.post('/', async (req, res) => {
  try {
    const biryani = await Biryani.create(req.body);

    res.status(201).json({
      success: true,
      message: 'Biryani successfully added to menu!',
      data: biryani
    });
  } catch (error) {
    // Handle Mongoose validation errors
    if (error.name === 'ValidationError') {
      const messages = Object.values(error.errors).map(err => err.message);
      return res.status(400).json({
        success: false,
        message: 'Validation failed',
        errors: messages
      });
    }

    res.status(500).json({
      success: false,
      message: 'Server error: ' + error.message
    });
  }
});

export default router;

Example request (POST /api/biryani):

{
  "name": "Chicken Dum Biryani",
  "restaurant": "Hyderabad Biryani House",
  "price": 250,
  "category": "Chicken",
  "spicyLevel": "High",
  "description": "Authentic Hyderabadi Dum Biryani with tender chicken"
}

Alternatives to Model.create():

  • new Model(data).save() — Create instance then save (useful for pre-save logic)
  • Model.insertMany([data1, data2]) — Insert multiple documents at once
  • Model.create(data) — Cleanest, returns the document directly

When the request body matches the schema, Mongoose validates and saves automatically. If validation fails, it throws a ValidationError with detailed messages for each field.

03Read — Model.find() aur Model.findById() se Data Dhoondhna

Read operations query the database to find documents. Mongoose provides several methods for different querying needs.

1. Find All Documents — Model.find()

// GET /api/biryani — Saare biryanis dikhao
router.get('/', async (req, res) => {
  try {
    const filter = {};

    // Support query params for filtering
    if (req.query.category) {
      filter.category = req.query.category;
    }
    if (req.query.spicyLevel) {
      filter.spicyLevel = req.query.spicyLevel;
    }
    if (req.query.isAvailable) {
      filter.isAvailable = req.query.isAvailable === 'true';
    }

    const biryanis = await Biryani.find(filter)
      .sort({ price: 1 })        // 1 = ascending, -1 = descending
      .select('name price category spicyLevel')  // Only these fields
      .limit(10);                 // Max 10 results

    res.json({
      success: true,
      count: biryanis.length,
      data: biryanis
    });
  } catch (error) {
    res.status(500).json({ success: false, message: error.message });
  }
});

2. Find by ID — Model.findById()

// GET /api/biryani/:id — Ek specific biryani dikhao
router.get('/:id', async (req, res) => {
  try {
    const biryani = await Biryani.findById(req.params.id);

    if (!biryani) {
      return res.status(404).json({
        success: false,
        message: 'Biryani nahi mila bhai! Yeh ID exist nahi karti.'
      });
    }

    res.json({ success: true, data: biryani });
  } catch (error) {
    // Handle invalid ObjectId format
    if (error.name === 'CastError') {
      return res.status(400).json({
        success: false,
        message: 'Invalid ID format. Sahi ObjectId daalo bhai!'
      });
    }
    res.status(500).json({ success: false, message: error.message });
  }
});

Other Read Methods:

  • Model.findOne(filter) — Find first document matching filter
  • Model.find({ price: { $gte: 200, $lte: 500 } }) — Find by price range
  • Model.find({ name: { $regex: 'chicken', $options: 'i' } }) — Case-insensitive search
  • Model.countDocuments(filter) — Count matching documents (no data transfer)
  • Model.exists(filter) — Check if document exists (returns boolean)
04Update — Model.findByIdAndUpdate() se Data Badalna

Update operations modify existing documents. The most useful method is findByIdAndUpdate().

// PUT /api/biryani/:id — Biryani update karo (price badhao ya description)
router.put('/:id', async (req, res) => {
  try {
    const biryani = await Biryani.findByIdAndUpdate(
      req.params.id,    // ID of document to update
      req.body,         // New data
      {
        new: true,      // Return the UPDATED document (not the old one)
        runValidators: true  // Run schema validation on the new data
      }
    );

    if (!biryani) {
      return res.status(404).json({
        success: false,
        message: 'Biryani nahi mila! Pehle ID check karo.'
      });
    }

    res.json({
      success: true,
      message: 'Biryani updated successfully!',
      data: biryani
    });
  } catch (error) {
    if (error.name === 'ValidationError') {
      const messages = Object.values(error.errors).map(e => e.message);
      return res.status(400).json({ success: false, errors: messages });
    }
    res.status(500).json({ success: false, message: error.message });
  }
});

Critical: Don't forget { new: true }!

By default, findByIdAndUpdate() returns the old document before the update. This is a massive trap — you'll get the old price, old name, and think nothing changed!

Always use { new: true } to get the updated document back.

Other Update Methods:

  • Model.findOneAndUpdate(filter, data, options) — Update first match
  • Model.updateOne(filter, data) — Update first match (returns count, not doc)
  • Model.updateMany(filter, data) — Update all matches
  • Model.findByIdAndUpdate(id, { $inc: { price: 50 } }) — Increment price by 50

The $inc operator is great for counters and numeric updates — no need to read the current value first!

05Delete — Model.findByIdAndDelete() se Data Hataana

Delete operations remove documents from the collection. Use with caution — there's no undo button (unless you have backups).

// DELETE /api/biryani/:id — Biryani hatao menu se
router.delete('/:id', async (req, res) => {
  try {
    const biryani = await Biryani.findByIdAndDelete(req.params.id);

    if (!biryani) {
      return res.status(404).json({
        success: false,
        message: 'Biryani mila hi nahi — kya delete karun bhai?'
      });
    }

    res.json({
      success: true,
      message: 'Biryani deleted from menu. RIP biryani. 🙏',
      data: {}
    });
  } catch (error) {
    if (error.name === 'CastError') {
      return res.status(400).json({
        success: false,
        message: 'Invalid ID format. Sahi ObjectId daalo!'
      });
    }
    res.status(500).json({ success: false, message: error.message });
  }
});

Other Delete Methods:

  • Model.findOneAndDelete(filter) — Delete first match
  • Model.deleteOne(filter) — Delete first match (returns count)
  • Model.deleteMany(filter) — Delete all matching documents

Putting it all together — Full CRUD Router:

// routes/biryani.routes.js — Pura CRUD ek saath
import express from 'express';
import Biryani from '../models/Biryani.js';

const router = express.Router();

router.post('/',   createBiryani);   // C - Create
router.get('/',    getAllBiryanis);   // R - Read all
router.get('/:id', getBiryaniById);   // R - Read one
router.put('/:id', updateBiryani);    // U - Update
router.delete('/:id', deleteBiryani); // D - Delete

export default router;

// Controller functions (separate file for production)
async function createBiryani(req, res) { ... }
async function getAllBiryanis(req, res) { ... }
async function getBiryaniById(req, res) { ... }
async function updateBiryani(req, res) { ... }
async function deleteBiryani(req, res) { ... }

Important: Always send appropriate HTTP status codes with delete responses. 200 OK means "deleted successfully." Some clients expect 204 No Content for deletes, but 200 with a success message is more informative for development.

Key Takeaways

  • ✅ CRUD = Create (POST), Read (GET), Update (PUT), Delete (DELETE)
  • ✅ Model.create(data) — Creates and returns a new document with validation
  • ✅ Model.find(filter) — Returns array of matching documents
  • ✅ Model.findById(id) — Returns single document by _id, or null if not found
  • ✅ Model.findByIdAndUpdate(id, data, { new: true, runValidators: true }) — Update with validation
  • ✅ ALWAYS use { new: true } or you get the OLD document back!
  • ✅ Model.findByIdAndDelete(id) — Delete and return the deleted document
  • ✅ Handle CastError for invalid ObjectId format and ValidationError for schema violations
  • ✅ Use query chaining: .sort(), .select(), .limit(), .populate() for powerful queries
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase