MongoDB is a document-oriented NoSQL database that stores data in flexible JSON-like structures called BSON documents, making it ideal for applications requiring rapid development and scalability without rigid schemas.
Traditional relational databases like PostgreSQL and MySQL enforce strict table schemas—every row must have the same columns. MongoDB abandons this rigidity. Instead of tables and rows, MongoDB uses collections and documents. A document is essentially a JSON object containing key-value pairs, arrays, and nested objects. This flexibility lets you evolve your data structure as your application grows without painful migrations.
MongoDB's popularity stems from several factors: it's developer-friendly, horizontally scalable, performs well with unstructured data, and integrates seamlessly with modern JavaScript frameworks. If you're building a Node.js app, a mobile backend, or a content management system, MongoDB often feels more natural than SQL databases.
The database stores documents in collections. Think of a collection like a table, but without enforcing consistent column structures across documents. You might have user documents with vastly different fields—one document could have a phone number while another doesn't, and that's perfectly fine.
A MongoDB document is the basic unit of data. It's a set of key-value pairs written in BSON format (Binary JSON). Here's a typical user document:
{
"_id": ObjectId("507f1f77bcf86cd799439011"),
"name": "Sarah Chen",
"email": "[email protected]",
"age": 28,
"tags": ["developer", "python", "mongodb"],
"address": {
"street": "123 Main St",
"city": "Portland",
"state": "OR",
"zip": "97201"
},
"createdAt": ISODate("2024-06-15T10:30:00Z")
}
Every document has an _id field that uniquely identifies it. MongoDB auto-generates this as an ObjectId if you don't provide one. Documents can contain primitive types (strings, numbers, booleans), arrays, nested objects, and dates.
Collections group related documents together. Unlike SQL tables, collections don't enforce a schema by default—you can insert documents with completely different structures into the same collection. Most applications do establish a consistent structure within a collection for practical reasons, but MongoDB grants you flexibility.
Databases contain collections. A MongoDB instance can host multiple databases, and each database contains multiple collections. You might have a production database and a testing database, each with their own collections for users, posts, comments, and so on.
MongoDB runs on Windows, macOS, and Linux. You have two main options: install it locally or use MongoDB Atlas, a cloud-hosted service.
Download MongoDB from mongodb.com/try/download/community. After installation, start the MongoDB daemon:
mongod
This starts the server on localhost:27017. In another terminal, launch the MongoDB shell to interact with the database:
mongosh
You're now connected and ready to execute commands.
MongoDB Atlas (cloud.mongodb.com) removes installation hassles. Sign up, create a cluster, and get a connection string instantly. This is ideal if you want to skip infrastructure management. You'll connect via a URI like:
mongodb+srv://username:[email protected]/dbname
Insert a single document with insertOne():
db.users.insertOne({
name: "Alex Jordan",
email: "[email protected]",
role: "admin",
active: true
})
Insert multiple documents with insertMany():
db.users.insertMany([
{ name: "Jordan Lee", email: "[email protected]" },
{ name: "Morgan Davis", email: "[email protected]" },
{ name: "Casey Williams", email: "[email protected]" }
])
Find all documents in a collection:
db.users.find()
Find documents matching criteria:
db.users.find({ role: "admin" })
Find with multiple conditions:
db.users.find({ role: "admin", active: true })
Use operators for complex queries:
db.users.find({ age: { $gt: 25 } }) // age greater than 25
db.users.find({ status: { $in: ["active", "pending"] } }) // status is active or pending
db.users.find({ name: { $regex: "alex", $options: "i" } }) // case-insensitive search
Find just one document:
db.users.findOne({ email: "[email protected]" })
Update a single document:
db.users.updateOne(
{ email: "[email protected]" },
{ $set: { role: "moderator" } }
)
Update multiple documents:
db.users.updateMany(
{ role: "user" },
{ $set: { active: true } }
)
The $set operator updates specific fields without touching others. Other useful operators include $inc (increment), $push (add to array), and $unset (remove field):
db.users.updateOne(
{ _id: ObjectId("507f1f77bcf86cd799439011") },
{ $inc: { loginCount: 1 } }
)
db.users.updateOne(
{ _id: ObjectId("507f1f77bcf86cd799439011") },
{ $push: { tags: "newbie" } }
)
Delete a single document:
db.users.deleteOne({ email: "[email protected]" })
Delete multiple documents:
db.users.deleteMany({ active: false })
Be careful with delete operations—they're permanent. Always test your filter criteria with a find() query first.
As your collections grow, queries slow down without indexes. An index tells MongoDB how to quickly locate documents matching your query criteria. Create an index on a commonly queried field:
db.users.createIndex({ email: 1 })
The 1 means ascending order; use -1 for descending. For compound indexes (multiple fields):
db.users.createIndex({ role: 1, active: 1 })
Check existing indexes:
db.users.getIndexes()
Indexes speed up reads but slow down writes (inserts, updates, deletes). Index strategically on fields you query frequently.
The aggregation pipeline processes documents through stages, transforming data at each step. It's MongoDB's answer to SQL's GROUP BY and JOIN operations. Here's a basic example:
db.users.aggregate([
{ $match: { role: "admin" } },
{ $group: { _id: "$status", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
])
This pipeline has three stages: $match filters documents, $group groups by status and counts them, and $sort orders by count descending. Aggregation handles complex analytics without fetching all documents into your application code.
Most developers use MongoDB drivers in their programming language. For Node.js, the popular choice is Mongoose—an ODM (Object Document Mapper) that provides schema validation and helper methods. Install it via npm:
npm install mongoose
Connect to MongoDB:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true
});
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', () => console.log('Connected to MongoDB'));
Define a schema and model:
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
age: Number,
createdAt: { type: Date, default: Date.now }
});
const User = mongoose.model('User', userSchema);
Now use the model to interact with the database—queries return promises that integrate cleanly with async/await. For other languages, MongoDB offers official drivers for Python, Java, C#, Go, and more.
Don't embed massive arrays inside documents—MongoDB limits document size to 16MB. If you have a user with millions of orders, store orders in a separate collection and reference them.
Don't assume MongoDB handles all your validation. Use schema validation in Mongoose or MongoDB's built-in schema validation to catch bad data early.
Do denormalize when it makes sense. Unlike SQL, duplicating small amounts of data across documents is often faster than querying multiple collections. If a user has a role field used in every query, store the role in the user document rather than joining to a separate roles collection.
Do index your queries. Run db.collection.explain("executionStats") to see if a query uses an index. If execution stats show many documents examined, create an index.
Don't store plain passwords. Hash them with bcrypt before inserting. The same applies to sensitive data—encrypt at rest if required by compliance rules.
You now understand documents, collections, CRUD operations, and basic queries. The next level involves mastering aggregation pipelines for complex analytics, implementing proper data validation with schemas, and optimizing query performance through indexes and explain plans. Explore transactions if you need ACID guarantees across multiple documents. Build a small project—maybe a blog or task manager—to solidify these concepts through hands-on practice.
MongoDB shines when your data is semi-structured, your application demands rapid iteration, and you want developers to work directly with JSON. It's not a replacement for SQL databases in all scenarios, but it's a powerful tool worth mastering.
For further learning, check out our databases chapter for related concepts, explore our SQL guide to understand relational database fundamentals you can compare against MongoDB, and read about database design patterns to architect scalable systems.
Neither is universally "better." MongoDB excels with unstructured data, rapid prototyping, and horizontal