CRUD Operations — Mongoose Se Data Likho-Padho
CRUD is the foundation of all database operations. Mongoose makes each operation clean and async-friendly.
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:
| Operation | HTTP Method | Mongoose Method | SQL Equivalent |
|---|---|---|---|
| Create | POST | Model.create(data) | INSERT INTO |
| Read (all) | GET | Model.find(filter) | SELECT * FROM |
| Read (one) | GET /:id | Model.findById(id) | SELECT * WHERE id= |
| Update | PUT /:id | Model.findByIdAndUpdate(id, data) | UPDATE SET |
| Delete | DELETE /:id | Model.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!
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 onceModel.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.
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 filterModel.find({ price: { $gte: 200, $lte: 500 } })— Find by price rangeModel.find({ name: { $regex: 'chicken', $options: 'i' } })— Case-insensitive searchModel.countDocuments(filter)— Count matching documents (no data transfer)Model.exists(filter)— Check if document exists (returns boolean)
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 matchModel.updateOne(filter, data)— Update first match (returns count, not doc)Model.updateMany(filter, data)— Update all matchesModel.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!
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 matchModel.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
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login