MongoDB 101: The Only Guide You Need to Get Productive Fast

Have you ever wondered how databases store and display data? It doesn’t blow your mind that how it stores and how it shows?
Let me introduce you to MongoDB, a powerful and flexible database solution that will answer your questions and more. In this guide, we'll explore the unique features of MongoDB and how it stands out from traditional databases.
Let me walk you through MongoDB and answer this question.
What is MongoDB and how it is different from other DB?
MongoDB is dev favourite Database which easy to get started with database and mostly all CRUD(Create Read Update Delete) based application can be easily build by this . You can tweak changes at any point in mongodb without touching your codebase.
MongoDB is a NoSQL document-oriented database, which means:
Data is stored as JSON-like documents.
Collections hold documents, not rows.
Store data in a
schema-less
fashion. Extremely lean and fast way to store data.It’s
schema-less
properties make it ideal to for bootstrapping a project fast.
what is this schema-less?
Different rows can have different schema
(keys/types)
How to connect to MongoDB?
Here’s full procedure to connect with MongoDB from i learned.
Now, what i am gonna do i am gonna generate a connection instance with mongo so, you’re gonna learn how DB have a convo with your server.
Steps for generating connection instance:
Create your NodeJs server.
Create your .env file to store your mongodb uri which you generated from video.
Now, there is always folder structure in your project whether it is frontend or backend make a folder of db or lib and then add a file any name like connectDB.js
- Now, Install dependencies npm install mongoose dotenv .
import mongoose from 'mongoose'//now this is the package that you installed //mongoose is makes it easier to interact with your MongoDB database by allowing you to define schemas, //apply validation, export const connectDB = async () => { //this is connectDb function which you call in your index.js try { await mongoose.connect(`${process.env.MONGO_URI}`) console.log("connected"); } catch (error) { console.log("Connection Error",error); } }
Make a file generally name index.js
```javascript import dotenv from 'dotenv'; //It loads environment variables from a .env file into process.env in your Node.js app. dotenv.config({ path: '../.env' }); import { connectDB } from "./db/connectDB.js"; import { app } from "./app.js";
connectDB() .then(() => app.listen(3000,()=>{ console.log("Listening on") } ))
Now, you are all set to work with your MongoDB Database.
## Defining Schemas With Mongoose
So, I know you are wondering what is schema — Schema is just a skeleton/Blueprint of your database. Schema is the very first step before doing anything in db. It can lets you write the structure of your data,define types and validation etc.
Basic syntax:
```javascript
import mongoose from 'mongoose'
const userSchema = new mongoose.Schema({ //now in this function i created a mongoose schema
username: { type: String, required: true }, // which takes username,email,age,createdAt these are
email: { type: String, unique: true }, // fields we are taking from user we setted type and their field options
age: { type: Number, default: 18 },
createdAt: { type: Date, default: Date.now },
})
export const User = mongoose.model('User',userSchema)
Now, you can use User to .create(), .find() etc.
These Schemas stored in body in BSON(Binary JSON) format with id which _id . This ._id is mongoose schema object id which can used in controllers to perform CRUD operations using mongodb operators.
{
"_id": "64b6e77f836ab5123c96e312",
"username": "ayush",
"email": "ayush@example.com",
"age": "18",
"createdAt": "2023-09-02T10:12:33.000Z",
}
Schema Field Options (With Examples)
Option | Purpose | Example |
type | Defines the data type | String , Number , Boolean , Date |
required | Field must be present | required: true |
default | Value to use if none is provided | default: Date.now |
unique | Ensures no duplicate values | unique: true |
enum | Limits to specific values | enum: ['user', 'admin'] |
min /max | Min/max for numbers | min: 0 , max: 100 |
validate | Custom validator function | validate: (val) => val.length > 3 |
We can pass ref to one mongoose to other mongoose schema in other words we can connect two or more schema between each other.
//Now this code is Submission model in which hackathonId referencing from Hackathon model
//and teamId referencing from team model
const SubmissionSchema = new mongoose.Schema({
hackathonId:{
type: mongoose.Schema.Types.ObjectId,
ref: 'Hackathon',
},
teamId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Team",
required: true
},
submissionUrl: {
type: String,
required: true
},
gitUrl:{
type:String,
required:true
},
submittedAt: {
type: Date,
default: Date.now
}
});
In this model this is submission model in which as you can see there is two joining Hackathon id and Team id
which is from Hackathon model and Team model it is referencing from their Object id.
In Hackathon id and Team id their type is mongoose.Schema.Types.ObjectId because leaderId
will store the _id
of a user from the User
collection. That _id
is always of type ObjectId
.
The ref: "User"
part is saying: “Hey Mongoose, this Object id belongs to the User
model so when I populate it, go find it in the users
collection.”
If you want to learn more about different Schemas go to my repo . You can find different models.
Handling CRUD operations , Query functions, MongoDB operators deep dive
First thing first now we have to write controllers for working with MongoDB by manipulating schemas or models that you have written. There are some typical HTTP request types that is write in controllers or services.
Create Requests (POST)
Inserts a new document into a MongoDB collections. It means that sending objects into backend where you can fetch from this.
//its using .create() oeprator it is creating a new user and saving that object in database. const newUser = await User.create({ username: "ayush", email: "ayush@gmail.com", password: "hashed-password" });
Read/fetch Requests (GET)
Fetches the document from database to read or to just get all the users or any other users which follow your operator condition.
//.find() → Gets all documents matching filter const users = await User.find({ country: "India" }); // .findOne() → Gets the first matching document const user = await User.findOne({ username: "ayush" }); // .findById() → Finds a document by its _id const user = await User.findById("64fb2a9e3a4d2e1c45c7b6f9"); // .populate() → Fills referenced data const post = await Post.findById(postId).populate("author");
Update Requests (PUT/PATCH)
PUT and PATCH both works for updating any User id , doc , Object etc.
PUT replace/updates the entire resource or object whereas PATCH updated part of a resource not full resource. e.g: PUT updates/replace entire user profile and on the other hand PATCH updates only username and email of the user profile.
{ //this is mongodb object _id: "123", username: "ayush", age: 21, skills: ["MongoDB", "Node.js"] } // PUT you send put request for updating username and age { "username": "ayushk", "age": 22 } //Result { _id: "123", username: "ayushk", age: 22 // skills field is now gone (replaced) } //now you send PATCH request { "age": 23 } //Result { _id: "123", username: "ayush", age: 23, //age is updated the remaining as it is skills: ["MongoDB", "Node.js"] }
Delete Request (DELETE)
Delete request as the name suggest it’s just delete the object from the document. The one you wanted to delete your use get request operators and write a condition the one you wanted to delete.
// now in this there is a user model in which you are using get operator to find the _id of user await User.findByIdAndDelete(userId); // now in this using delete operator .deleteMany() and .deleteOne() in this which deleting that user //whose role schema is inactive await User.deleteMany({ role: "inactive" });
Different MongoDB operators and query functions
These all the things that are working here there crucial steps is how you are using MongoDB operators in query functions . All the things that is working here are working on the basis of operators. Now, I will be explaining all the query function and MongoDB operators.
Let’s see all the query function methods:
find()
- Returns all documents matching the filter on the basis of used operators.
//now in this hackathonId we are getting from params and then from submission model finding the
// hackathonid and listing them all
const {hackathonId} = req.params;
const submissions = await Submission.find({hackathonId:hackathonId});
findOne()
- Returns the first document that matches the filter.
//In this fineOne is used to find the existing verified user from the user model it is finding on the
// basis of the username field and isVerified field which is true.
const existingVerifiedUserByUsername = await UserModel.findOne({
username,
isVerified: true,
});
findById()
- Finds a document by its
_id
.
//In this finding existing hackathon from the hackathon model on the basis of hackathonId by using
//findById()
const hackathon = await Hackathon.findById(hackathonId);
findOneAndUpdate()
- Finds a document by filter and updates it.
//The findOneAndUpdate finding username exaclty ayush and updating
const filter = { username: 'ayush' };
const updatedUser = await User.findOneAndUpdate(filter);
findByIdAndUpdate()
- Like
findOneAndUpdate()
but by_id
.
//In this finding user id from user model and by using findByIdAndUpdate.Finding _id and then updating
//fullName and email only and after updating updates all the new changes.
const user = await User.findByIdAndUpdate(
req.user?._id,
{
$set: { //mongodb operator which update the specific fields in a document without affecting others
fullName,
email
}
},
{new: true} //after updation it updates all the new changes
)
findOneAndDelete()
- Finds a document by filter and deletes it.
//This findOneAndDelete finds username from the User model and delete's it.
const delete = await User.findOneAndDelete({ username: 'ayush' });
findByIdAndDelete()
- Like above, but by
_id
.
const delete = await User.findByIdAndDelete(userId)
findOneAndReplace()
- Replaces the entire document with the given one.
//Now in this its finding username from User model and replacing entire doc with thw new one.
await User.findOneAndReplace(
{ username: 'ayush' },
{ username: 'newuser', age: 20 }
);
Let’s see now MongoDB operators:
Logical Operators
Operator | Description | Example Use |
$and | All conditions must be true | { $and: [ { age: { $gte: 18 } }, { verified: true } ] } |
$or | At least one condition true | { $or: [ { role: "admin" }, { role: "mod" } ] } |
$not | reverses the condition | { age: { $not: { $gt: 25 } } } |
$nor | None of the conditions true | { $nor: [ { age: 25 }, { name: "Ayush" } ] } |
These logical operators used when you have to combine or modify multiple conditions to precisely filter the documents you want to retrieve, update, or delete. Like when you have to find all products that are in the "Electronics" category AND have a price less than $50. When you have to find all users who are either "Admins" OR have a subscription plan of "Premium".
Let’s see these operator applying in code:
//$and
//In this we have a collection of products with price and inStock field. We want to get all the
//products where the price is greater than and inStock also greater than 0. We are using $and so
//both condition should be true.
db.products.find({
$and: [
{ price: { $gt: 100 } },
{ inStock: { $gt: 0 } }
]
})
//$or
// In this we are finding hackathonId from the team model and after getting hackathonId
// using $or operator for getting leaderid and memeberid . Now one of this atleast should be true .
//Then we can get existing team from the team model
const existingTeam = await Team.findOne({
hackathonId: team.hackathonId,
$or: [
{ leaderId: userId },
{ memberIds: userId }
]
});
//$not
//Find all articles that have a status other than 'published'.
db.articles.find({
status: { $not: { $eq: 'published' } }
})
//$nor
//Let's say we have a collection of employees with a department field.Now,We use $nor this query will
//return all employees whose department field is not 'Marketing' and 'Sales' and it also returns also
//those employees who have no department.
db.employees.find({
$nor: [
{ department: 'Marketing' },
{ department: 'Sales' }
]
}
Comparison Operators
Operator | Description | Example Use |
$eq | Equal to | { age: { $eq: 25 } } |
$ne | Not equal to | { status: { $ne: "inactive" } } |
$gt | Greater than | { score: { $gt: 80 } } |
$gte | Greater than or equal to | { age: { $gte: 18 } } |
$lt | Less than | { price: { $lt: 1000 } } |
$lte | Less than or equal | { level: { $lte: 5 } } |
$in | In array of values | { city: { $in: ["Delhi", "Mumbai"] } } |
$nin | Not in array | { role: { $nin: ["admin", "mod"] } } |
These are Comparison operator which is used for comparison between two elements or more than two elements. In the previous example logical operator we used $and and used the comparison operator for in which we compared price and In stock field.
Let’s see these operator applying in code:
// $eq – Equals
User.find({ age: { $eq: 25 } });//In this code finding users age from User model with $eq equals to 25
// $ne – Not Equals
User.find({ age: { $ne: 25 } });//In this code finding users age from User model with $ne not equals to 25
// $gt – Greater Than
User.find({ age: { $gt: 30 } });//In this code finding users age from user model with age is greater than
//30 using $gt
//$gte – Greater Than or Equal To
User.find({ age: { $gte: 18 } });//In this code finding users age from user model with age is
//greater than or equal to
//$lt – Less Than
User.find({ age: { $lt: 18 } });//In this code finding users age from user model with age is less than 18.
// Users younger than 18
//$lte – Less Than or Equal To
User.find({ age: { $lte: 60 } });//In this code finding users age from user model with age less than or equal to 60.
//$in – Matches any value in an array
//Now,this code is finding all the users whose _id matches with response.memberIds and the "name" is
//telling the mongoose to return only name field of each matched document.
const users = await User.find({ _id: { $in: response.memberIds } }, "name");
// $nin – Not in array
User.find({ country: { $nin: ['India', 'USA'] } });//In this code finding users from user model whose not from India or Usa
//In this code finding users whose age is between 18 and 30 and role is either student or intern and active status equals to true.
User.find({
age: { $gte: 18, $lte: 30 },
role: { $in: ['student', 'intern'] },
active: { $eq: true }
});
Update Operators
Operator | Description | Example |
$set | Sets a field | { $set: { age: 21 } } |
$inc | Increment | { $inc: { age: 1 } } |
$push | Push to array | { $push: { tags: "new" } } |
$pull | Remove from array | { $pull: { tags: "old" } } |
These are update Operators which is used in updateOne(),findIdAndUpdate().etc. It is used to modify document fields and used to manipulate array fields. These operators is crucial for modifying documents without replacing the entire document
Let’s see these operator applying in code:
//$set
//In this firstly we are finding hackathonid from hackathon model and updating the banner by $set
// operator which sets the banner field with banner.url
await Hackathon.findByIdAndUpdate(
hackathon._id,
{
$set: { banner: banner.url }
});
//$inc
//In this code updating _id with postId and increasing the likes by 1 using $inc.
//If initially the likes were 5 after increment it will become 6.
await Post.updateOne(
{ _id: postId },
{ $inc: { likes: 1 } }
);
//$push
//In this code finding user._id from user model and Updates that user’s ownedHackathons array
//by adding the hackathon._id to it.Then pushing ownedHackathon field in the array.
await User.findByIdAndUpdate(user._id, {
$push: { ownedHackathons: hackathon._id }
});
//$pull
//In this code converting string messageid into _id objectid for ensuring correctly matching types.
//Then finding user._id from user model then removes one message from the user's messages array
// where message id equals messageObjectId.
const messageObjectId = new mongoose.Types.ObjectId(messageid);
const updateResult = await UserModel.updateOne(
{ _id: user._id },
{ $pull: { messages: { _id: messageObjectId } } }
);
Aggregation Pipelines and Aggregation Operators
Now ,we are at the end of this blog and now this is the final villain or you can say the General Radahn (Hardest boss of Elden ring game) of the MongoDB. Aggregation pipelines contains stages just like in a video game there are stages from which you have to go and filter just like that documents filter, shape and compute data from MongoDB collections.
Aggregation pipeline used when you have to group data or group objects , get nested info, join collection with left join , etc. It is MongoDB’s answer to all SQL’s GROUP BY , JOIN and complex analytics .
It follows a chain of stages by using aggregation operators. Each stage gets the result of the previous one.
It use .aggregate([{},{},{}]) and this is the structure of how pipelines can be written. The values that we get after writing pipelines is array’s.
Now, we are gonna learn how to write pipelines and also gonna learn there operators all in one code:
Learn More about Aggregation operators and pipelines
//This function fetch's user's channel along with how many subscribers the user has
//How many other channels this user has subscribed to ,Whether the currently logged-in user is subscribed to this channel
//A selected subset of user profile fields (like avatar, name, email)
const getUserChannelProfile = asyncHandler(async(req,res) => {
const {username} = req.params //getting username from params
if(!username?.trim()) { //conditional statement for username field is missing
throw new ApiError(400, "username is missing")
}
const channel = await User.aggregate([ //follows the aggregate structure
{
$match: { //This is $match operator it filters like find() query
username: username.toLowerCase() //In this filters the User collection to find the username.
}
},
{
$lookup: {// This is $lookup operator.This joins two collections by localField and foreignField using from and as.
from: "subscriptions",//In this joins subscriptions collection to find all users susbcribed to this channel.
localField: "_id",//User's objectID
foreignField: "channel",//In the subscriptions collection there is channel field that refers to the channel from which user is susbcribed to.
as: "subscribers"//Result is stored as in a field called subscribers.
}
},
{
$lookup: {//this finds all the channels that the user has susbcribed to.
from: "subscriptions",
localField: "_id",
foreignField: "subscriber",//Matches susbscriptions where the user is the one subscribing.
as: "subscribedTo"//stored as field called subscribedTo
}
},
{
$addFields: {// This is $addFields operator which add new fields to the MongoDB Object.
//Now this adds subcribersCount, channelsSubscribedToCount, isSubscribed to the original MongoDB object.
subcribersCount: {
$size: "$subscribers" //this is $size operator to count the number of items in the susbcribers array $ is used in subscribers because it is a field
},
channelsSubscribedToCount: {
$size: "$subscribedTo" // counts how many channels the user follows.
},
isSubscribed: {
$cond: { // this is $cond operator which uses if then else on the basis of if condition it returns true or false.
if: {$in: [req.user?._id, "$subscribers.subscriber"]}, //checks if current user exists in the subscribers.subscriber array.
then: true,
else: false
}
}
}
},
{
$project: { //this is $project operator shapes the output(rename,select fields).
//this selects only specific fields to return in the final result.
fullName: 1,
username: 1,
subscribersCount: 1,
channelsSubscribedToCount: 1,
isSubscribed: 1,
avatar: 1,
coverImage: 1,
email: 1
}
}
])
Output of this :
{
"fullName": "Ayush Kumar",
"username": "ayush",
"subscribersCount": 42,
"channelsSubscribedToCount": 12,
"isSubscribed": true,
"avatar": "/avatars/ayush.jpg",
"coverImage": "/covers/bg.jpg",
"email": "ayush@example.com"
}
I’ve used these exact techniques in my own projects from authentication systems to full-stack dashboards and MongoDB has consistently delivered performance and simplicity.
MongoDB isn't just a "NoSQL alternative" it's a powerful, flexible engine that gives you control over your data with update operators, schema design, and the aggregation pipeline.
If you’re building anything serious not just CRUD apps .You need to understand how $set
, $push
, $pull
, $lookup
, $match
, $addFields
and other tools actually work under the hood.
Mongoose gives you structure, MongoDB gives you power. Master both —and you're no longer a copy-paste dev. You're in control.
Subscribe to my newsletter
Read articles from Ayush kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Ayush kumar
Ayush kumar
Full-stack dev obsessed with clean backend logic , clean frontend and turning ideas into working projects. I write what I build.