
MongoDB Tutorial: Databases, Collections, Query API and CRUD Operations
MongoDB is a document-oriented NoSQL database used to store data in flexible, JSON-like documents. Instead of organizing information only in tables and rows, MongoDB stores data as documents inside collections. This makes it useful for modern applications where data structures may change over time, such as web platforms, APIs, dashboards, content systems, ecommerce applications, analytics tools, and real-time services.
This article works as a detailed introduction to the MongoDB topics shown in the learning path: MongoDB Home, Get Started, Query API, Create Database, Collection, Insert, Find, Update, and Delete. The goal is not only to show commands, but also to explain how MongoDB thinks about data and how developers should use it in real projects.
If you already know SQL, MongoDB may feel different at first. SQL databases usually start with a strict schema, tables, rows, columns, joins, and relational constraints. MongoDB starts with documents, collections, flexible fields, embedded data, references, indexes, and a query language based on structured objects. Both approaches can be powerful, but they solve problems in different ways.
What Is MongoDB?
MongoDB is a NoSQL database that stores data as documents. A document is a structured object made of fields and values. The document format is similar to JSON, but internally MongoDB uses a binary format called BSON. BSON supports additional data types such as ObjectId, Date, Decimal128, Binary, and other types that are useful for database storage.
A simple MongoDB document can look like this:
{
"_id": ObjectId("665f2a1b8f2c9a0012a45c91"),
"name": "Adnan",
"email": "adnan@example.com",
"role": "developer",
"active": true,
"skills": ["PHP", "Laravel", "MongoDB"],
"profile": {
"city": "Samsun",
"country": "Turkey"
},
"createdAt": ISODate("2026-06-11T00:00:00Z")
}This document contains normal fields such as name and email, an array field called skills, and an embedded object called profile. This is one of the main strengths of MongoDB: related data can be stored together when it is usually read together.
In a relational database, the same information may be split into multiple tables such as users, user_skills, and user_profiles. In MongoDB, the data can be embedded inside one user document if that structure makes the application easier and faster to read.
MongoDB Home: The Big Picture
The MongoDB learning path usually begins with the big picture. Before writing commands, it is important to understand the main building blocks.
Database: A container for collections. For example, an application may use a database named blog, shop, school, or crm.
Collection: A group of documents. It is similar to a table in SQL, but it does not require every document to have exactly the same fields.
Document: A single record stored as a BSON object. It is similar to a row in SQL, but it can contain nested objects and arrays.
Field: A key inside a document. It is similar to a column name in SQL.
_id: A unique identifier automatically added to every document unless you provide your own unique value.
Query: A structured request used to find, filter, update, or delete documents.
Index: A data structure that helps MongoDB find documents faster.
The following comparison helps SQL developers understand MongoDB terminology.
SQL Database → MongoDB Database
SQL Table → MongoDB Collection
SQL Row → MongoDB Document
SQL Column → MongoDB Field
Primary Key → _id
WHERE condition → Query filter
JOIN → Embedding, references, or $lookup
INSERT → insertOne() / insertMany()
SELECT → find() / findOne()
UPDATE → updateOne() / updateMany()
DELETE → deleteOne() / deleteMany()MongoDB is not better than SQL in every situation, and SQL is not better than MongoDB in every situation. The right choice depends on the data model, consistency requirements, reporting needs, scalability needs, and how the application reads and writes data.
MongoDB Get Started
To get started with MongoDB, developers usually need three things: a MongoDB server, a way to connect to the server, and a tool or driver to run commands.
You can use MongoDB in different ways:
MongoDB Atlas: A managed cloud database service. It is useful when you do not want to manage the database server yourself.
Local MongoDB Server: Installed directly on your computer or server for development and testing.
Docker: Useful for local development because MongoDB can run inside a container.
MongoDB Compass: A graphical interface for viewing databases, collections, documents, and indexes.
mongosh: The MongoDB Shell used to run database commands from the terminal.
Application Drivers: Official libraries that allow Node.js, PHP, Python, Java, C#, Go, and other languages to communicate with MongoDB.
A local MongoDB server can be started with Docker like this:
docker run --name mongodb-demo -p 27017:27017 -d mongoThen you can connect using mongosh:
mongosh "mongodb://localhost:27017"Inside mongosh, you can check the current database with:
dbYou can list available databases with:
show dbsAt the beginning, MongoDB may not show a database until data is actually inserted into it. This is normal because MongoDB creates databases and collections lazily. In practice, this means that a database or collection becomes real when it receives data or is explicitly created.
MongoDB Connection String
Applications usually connect to MongoDB using a connection string. A basic local connection string looks like this:
mongodb://localhost:27017A connection string with authentication may look like this:
mongodb://username:password@localhost:27017/app_dbIn real projects, connection strings should not be hard-coded inside application files. They should be stored in environment variables, for example in a .env file or server configuration.
MONGODB_URI=mongodb://localhost:27017
MONGODB_DATABASE=app_dbThis keeps credentials safer and makes it easier to use different databases for local development, testing, staging, and production.
MongoDB Query API
The MongoDB Query API is the way developers read and write data in MongoDB. Unlike SQL, MongoDB queries are usually written as structured objects. These objects describe filters, update operations, projections, sorting rules, aggregation stages, and other database operations.
For example, an SQL query may look like this:
SELECT name, email
FROM users
WHERE active = true
ORDER BY created_at DESC
LIMIT 10;The MongoDB version can look like this:
db.users.find(
{ active: true },
{ name: 1, email: 1 }
).sort({ createdAt: -1 }).limit(10)The first object is the filter:
{ active: true }The second object is the projection:
{ name: 1, email: 1 }The sort rule uses -1 for descending order and 1 for ascending order:
{ createdAt: -1 }The Query API includes different types of operations:
Read operations: find documents using filters, projections, sorting, limits, and pagination.
Write operations: insert, update, replace, and delete documents.
Aggregation operations: transform, group, join, calculate, and reshape data using pipelines.
Index operations: create and manage indexes for performance.
Database and collection operations: create collections, list collections, drop collections, and manage validation rules.
The Query API is expressive because filters can contain operators. Common query operators include:
$eq: equals a value.
$ne: not equal.
$gt: greater than.
$gte: greater than or equal.
$lt: less than.
$lte: less than or equal.
$in: value exists inside a list.
$nin: value does not exist inside a list.
$and: combine multiple conditions where all must be true.
$or: combine multiple conditions where at least one must be true.
$exists: check whether a field exists.
$regex: match text using a regular expression.
For example, this query finds active users who are at least 18 years old:
db.users.find({
active: true,
age: { $gte: 18 }
})This query finds users whose role is either admin or editor:
db.users.find({
role: { $in: ["admin", "editor"] }
})This query finds users who are either active or verified:
db.users.find({
$or: [
{ active: true },
{ verified: true }
]
})The Query API is the foundation of MongoDB development. Insert, find, update, and delete operations all depend on understanding how filters and document structures work.
MongoDB Create Database
Creating a database in MongoDB is different from creating a database in many SQL systems. In MongoDB, you can switch to a database even if it does not exist yet. The database is actually created when you insert data into it or explicitly create a collection.
To switch to a database named blog_db, use:
use blog_dbNow the shell points to blog_db, but the database may not appear in show dbs until it contains data.
Insert a document to create the database automatically:
db.posts.insertOne({
title: "Learning MongoDB",
slug: "learning-mongodb",
status: "published",
createdAt: new Date()
})Now you can run:
show dbsThe database should appear because it now contains a collection and a document.
This lazy creation behavior is convenient for development, but in production developers should still plan database names, collection names, indexes, validation rules, users, permissions, and backup policies carefully.
Database Naming Best Practices
Database names should be clear and stable. A good database name describes the application or service that owns the data.
Use clear names such as blog_db, shop_db, crm_db, or analytics_db.
Avoid random names that do not explain the purpose of the database.
Avoid using the same database for unrelated applications.
Use separate databases or environments for development, testing, staging, and production.
Do not store production credentials directly inside code files.
A typical project may use different database names for each environment:
app_local
app_testing
app_staging
app_productionThis separation helps prevent accidental testing operations from damaging production data.
MongoDB Collection
A collection is a group of MongoDB documents. It is similar to a table in SQL, but it is more flexible. Documents inside the same collection can have different fields, although in professional applications it is still better to keep a consistent structure.
For example, a users collection may contain documents like this:
{
"_id": ObjectId("665f2a1b8f2c9a0012a45c91"),
"name": "Adnan",
"email": "adnan@example.com",
"role": "admin",
"active": true
}And another document in the same collection may include additional fields:
{
"_id": ObjectId("665f2a1b8f2c9a0012a45c92"),
"name": "Sara",
"email": "sara@example.com",
"role": "editor",
"active": true,
"profile": {
"city": "Istanbul",
"country": "Turkey"
},
"skills": ["Writing", "SEO", "Content Management"]
}MongoDB allows this flexibility, but flexibility does not mean chaos. In real applications, you should design documents carefully. A collection should have a clear purpose, and documents inside it should usually follow a predictable structure.
You can create a collection explicitly:
db.createCollection("users")You can also create a collection automatically by inserting the first document:
db.products.insertOne({
name: "Wireless Mouse",
price: 25,
stock: 100
})After inserting this document, MongoDB creates the products collection if it does not already exist.
Collection Naming Best Practices
Collection names should be simple, readable, and consistent. Many teams use plural names because a collection contains many documents.
Use names such as users, posts, orders, products, comments, and categories.
Keep naming style consistent across the project.
Avoid vague names such as data, items, or records unless they truly describe the domain.
Do not mix unrelated document types in the same collection just because MongoDB is flexible.
Create indexes based on how the application queries the collection.
For example, a blog application may have collections such as:
users
posts
categories
tags
comments
media
settingsAn ecommerce application may have:
customers
products
orders
payments
shipments
reviews
couponsSchema Design in MongoDB
MongoDB is schema-flexible, not schema-free in the practical sense. The database does not force every document to look exactly the same by default, but the application should still follow a clear data model.
The most important schema design question in MongoDB is whether related data should be embedded inside one document or referenced from another collection.
Embedding Data
Embedding means storing related data inside the same document. This is useful when the embedded data belongs strongly to the parent document and is usually read together with it.
{
"title": "MongoDB Basics",
"slug": "mongodb-basics",
"author": {
"name": "Adnan",
"email": "adnan@example.com"
},
"tags": ["mongodb", "database", "nosql"]
}Embedding can reduce the need for extra queries because the data is already inside the document.
Referencing Data
Referencing means storing an identifier that points to another document, similar to a foreign key idea in SQL, although MongoDB does not behave exactly like relational databases by default.
{
"title": "MongoDB Basics",
"slug": "mongodb-basics",
"authorId": ObjectId("665f2a1b8f2c9a0012a45c91"),
"tags": ["mongodb", "database", "nosql"]
}Referencing is useful when related data is large, reused in many places, updated frequently, or should remain separate for business reasons.
A simple rule is: embed data that is owned by the parent and read together; reference data that is shared, large, independent, or updated separately.
MongoDB Insert
Insert operations add new documents to a collection. MongoDB provides different insert methods, but the most common are insertOne() and insertMany().
insertOne()
The insertOne() method inserts a single document.
db.users.insertOne({
name: "Adnan",
email: "adnan@example.com",
role: "admin",
active: true,
createdAt: new Date()
})If you do not provide an _id field, MongoDB automatically creates one. The default value is usually an ObjectId.
The result usually contains an acknowledged status and the inserted id:
{
acknowledged: true,
insertedId: ObjectId("665f2a1b8f2c9a0012a45c91")
}insertMany()
The insertMany() method inserts multiple documents at once.
db.users.insertMany([
{
name: "Sara",
email: "sara@example.com",
role: "editor",
active: true,
createdAt: new Date()
},
{
name: "Omar",
email: "omar@example.com",
role: "user",
active: false,
createdAt: new Date()
}
])Batch inserts are useful when importing seed data, logs, products, messages, analytics records, or migration data.
Important Insert Rules
When inserting documents, keep these rules in mind:
Every document must have a unique _id.
If _id is not provided, MongoDB creates it automatically.
Documents in the same collection can have different fields, but consistent structure is better for maintainability.
Use correct data types from the beginning, especially for dates, numbers, booleans, and identifiers.
Do not store passwords as plain text. Always hash passwords in the application before storing them.
Avoid storing huge unbounded arrays inside a single document because documents have size limits and can become inefficient.
A good user document should avoid unnecessary ambiguity:
db.users.insertOne({
name: "Adnan",
email: "adnan@example.com",
role: "admin",
active: true,
loginCount: 0,
createdAt: new Date(),
updatedAt: new Date()
})This document uses boolean values for status, numeric values for counters, and Date values for timestamps. This makes future queries and updates easier.
MongoDB Find
Find operations read documents from a collection. The two most common methods are find() and findOne().
find()
The find() method returns all matching documents.
db.users.find()This command returns documents from the users collection. In real applications, you should usually use filters, projections, sorting, and limits instead of reading everything.
Find active users:
db.users.find({
active: true
})Find users with the role admin:
db.users.find({
role: "admin"
})Find users older than or equal to 18:
db.users.find({
age: { $gte: 18 }
})findOne()
The findOne() method returns one matching document.
db.users.findOne({
email: "adnan@example.com"
})This is useful when searching by a unique field such as email, username, slug, or _id.
Find with Projection
Projection controls which fields are returned. This is useful when a document contains many fields but the application only needs some of them.
db.users.find(
{ active: true },
{ name: 1, email: 1, role: 1 }
)This returns only name, email, role, and _id by default. If you do not want _id, set it to 0:
db.users.find(
{ active: true },
{ _id: 0, name: 1, email: 1, role: 1 }
)Projection improves clarity and can reduce network transfer when documents are large.
Find with Sort, Limit, and Skip
Sorting controls the order of returned documents.
db.users.find().sort({ createdAt: -1 })Limit controls how many documents are returned.
db.users.find().limit(10)Skip can be used for simple pagination:
db.users.find()
.sort({ createdAt: -1 })
.skip(20)
.limit(10)This returns page-like results, but large skip values can become inefficient. For large datasets, cursor-based pagination using indexed fields such as _id or createdAt is usually better.
Find with Logical Operators
The $and operator requires all conditions to be true.
db.users.find({
$and: [
{ active: true },
{ role: "editor" }
]
})In many cases, MongoDB allows a shorter form:
db.users.find({
active: true,
role: "editor"
})The $or operator requires at least one condition to be true.
db.users.find({
$or: [
{ role: "admin" },
{ role: "editor" }
]
})The $in operator is often cleaner when checking one field against multiple values:
db.users.find({
role: { $in: ["admin", "editor"] }
})Find in Arrays and Nested Documents
MongoDB can query array fields directly. For example, find users who have MongoDB in their skills:
db.users.find({
skills: "MongoDB"
})MongoDB can also query nested fields using dot notation:
db.users.find({
"profile.city": "Samsun"
})This is one of the reasons MongoDB is useful for document-based data models. Nested data can be queried without manually splitting everything into many tables.
MongoDB Update
Update operations modify existing documents. MongoDB provides several update methods, including updateOne(), updateMany(), and replaceOne().
The most important rule is this: in most cases, use update operators such as $set, $inc, $push, $addToSet, and $unset. Do not accidentally replace the whole document when you only want to change one field.
updateOne()
The updateOne() method updates the first document that matches the filter.
db.users.updateOne(
{ email: "adnan@example.com" },
{
$set: {
role: "admin",
updatedAt: new Date()
}
}
)The first object is the filter. The second object describes the update operation. Here, $set changes only the role and updatedAt fields.
updateMany()
The updateMany() method updates all documents that match the filter.
db.users.updateMany(
{ active: false },
{
$set: {
status: "inactive",
updatedAt: new Date()
}
}
)Use updateMany carefully. Always test the filter first with find() before running a large update.
db.users.find({ active: false })After confirming the result set, run the update.
Common Update Operators
MongoDB update operators allow precise modifications.
$set
Set or change field values:
db.users.updateOne(
{ email: "sara@example.com" },
{ $set: { active: true } }
)$unset
Remove a field from a document:
db.users.updateOne(
{ email: "sara@example.com" },
{ $unset: { temporaryToken: "" } }
)$inc
Increase or decrease a numeric value:
db.users.updateOne(
{ email: "adnan@example.com" },
{ $inc: { loginCount: 1 } }
)$push
Add a value to an array:
db.users.updateOne(
{ email: "adnan@example.com" },
{ $push: { skills: "Node.js" } }
)$addToSet
Add a value to an array only if it does not already exist:
db.users.updateOne(
{ email: "adnan@example.com" },
{ $addToSet: { skills: "MongoDB" } }
)The difference between $push and $addToSet matters. $push can create duplicate values, while $addToSet avoids duplicates for the same value.
Upsert in MongoDB
An upsert means update if the document exists, or insert a new document if it does not exist. This is useful for settings, counters, sync jobs, external identifiers, and idempotent operations.
db.users.updateOne(
{ email: "newuser@example.com" },
{
$set: {
name: "New User",
role: "user",
active: true,
updatedAt: new Date()
},
$setOnInsert: {
createdAt: new Date()
}
},
{ upsert: true }
)Here, $set applies on update or insert, while $setOnInsert applies only when a new document is created.
replaceOne()
The replaceOne() method replaces the entire matching document except for the _id field. It should be used only when you intentionally want to replace the full document structure.
db.users.replaceOne(
{ email: "adnan@example.com" },
{
name: "Adnan Mehrat",
email: "adnan@example.com",
role: "admin",
active: true,
updatedAt: new Date()
}
)Be careful: fields not included in the replacement document can be removed. For most normal changes, updateOne() with $set is safer than replaceOne().
MongoDB Delete
Delete operations remove documents from a collection. MongoDB provides deleteOne() and deleteMany().
deleteOne()
The deleteOne() method deletes the first document that matches the filter.
db.users.deleteOne({
email: "omar@example.com"
})This is useful when deleting by a unique field such as _id or email.
deleteMany()
The deleteMany() method deletes all documents that match the filter.
db.users.deleteMany({
active: false
})Before using deleteMany(), always run find() with the same filter to confirm which documents will be removed.
db.users.find({
active: false
})Then run deleteMany() only after you are sure.
Dangerous Delete Operations
The most dangerous delete command is an empty filter:
db.users.deleteMany({})This deletes all documents in the users collection. It may be useful in development or testing, but it is dangerous in production.
Dropping an entire collection is also destructive:
db.users.drop()Dropping a database is even more dangerous:
db.dropDatabase()Production systems should protect destructive operations using backups, permissions, careful scripts, review processes, and monitoring.
Soft Delete Instead of Hard Delete
Many applications avoid deleting important records immediately. Instead, they use a soft delete approach. This means the document remains in the database, but it is marked as deleted.
db.users.updateOne(
{ email: "sara@example.com" },
{
$set: {
deletedAt: new Date(),
active: false
}
}
)Then normal queries exclude deleted documents:
db.users.find({
deletedAt: { $exists: false }
})Soft delete is useful for audit trails, restore features, admin review, compliance needs, and avoiding accidental permanent loss.
A Complete CRUD Example
The following example shows a small MongoDB workflow from database selection to insert, find, update, and delete.
use tutorial_db
db.users.insertOne({
name: "Adnan",
email: "adnan@example.com",
role: "developer",
active: true,
skills: ["PHP", "Laravel"],
loginCount: 0,
createdAt: new Date(),
updatedAt: new Date()
})
db.users.findOne({
email: "adnan@example.com"
})
db.users.updateOne(
{ email: "adnan@example.com" },
{
$set: {
role: "admin",
updatedAt: new Date()
},
$addToSet: {
skills: "MongoDB"
},
$inc: {
loginCount: 1
}
}
)
db.users.find(
{ active: true },
{ name: 1, email: 1, role: 1, skills: 1 }
).sort({ createdAt: -1 }).limit(10)
db.users.deleteOne({
email: "adnan@example.com"
})This example demonstrates the basic flow used in almost every MongoDB application. A real system may add authentication, validation, indexes, transactions, backups, and application-level services, but CRUD operations remain the foundation.
MongoDB and Indexes: A Practical Note
Although indexes are not the main topic of this article, they are important for real MongoDB performance. Without indexes, MongoDB may need to scan many documents to find matching results. With the right indexes, queries can become much faster.
For example, if users are often searched by email, create an index:
db.users.createIndex({ email: 1 }, { unique: true })This index helps MongoDB find users by email faster and also prevents duplicate email values.
If posts are often listed by status and creation date, a compound index may help:
db.posts.createIndex({ status: 1, createdAt: -1 })Indexes should be based on real query patterns. Too few indexes can make reads slow, while too many indexes can make writes heavier and increase storage usage.
MongoDB Validation: Keeping Flexible Data Clean
MongoDB allows flexible documents, but professional systems often need validation. Collection validation can help enforce required fields, field types, and basic structure.
For example, you can create a users collection with simple validation:
db.createCollection("users_validated", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "email", "role"],
properties: {
name: {
bsonType: "string"
},
email: {
bsonType: "string"
},
role: {
enum: ["admin", "editor", "user"]
},
active: {
bsonType: "bool"
}
}
}
}
})This does not replace application validation, but it adds another layer of protection at the database level. In serious applications, validation should exist in both the application and the database where appropriate.
Common Mistakes When Learning MongoDB
Beginners often make mistakes because MongoDB looks simple at first. The commands are easy to write, but good data modeling still requires discipline.
Using MongoDB like a relational database: Recreating many SQL-style tables and joins may ignore MongoDB's document model benefits.
Embedding too much data: Huge nested arrays or large embedded structures can become hard to update and inefficient.
Ignoring indexes: Queries may work with small data but become slow when collections grow.
Using inconsistent field names: Mixing created_at, createdAt, created_date, and dateCreated makes code harder to maintain.
Storing wrong data types: Storing numbers as strings or dates as plain text makes filtering and sorting harder.
Running updateMany or deleteMany without testing: Large write operations can damage data if the filter is wrong.
Ignoring security: Databases should use authentication, network restrictions, least-privilege users, and backups.
The best way to avoid these mistakes is to design collections based on application use cases, test queries with realistic data, and treat MongoDB as a serious database system, not just a flexible JSON storage tool.
When Should You Use MongoDB?
MongoDB is a strong choice when the application needs flexible documents, fast iteration, nested data, high write volume, scalable architecture, and data structures that do not fit naturally into strict relational tables.
Common use cases include:
Content management systems.
User profiles with flexible attributes.
Product catalogs with different product specifications.
Event logs and activity streams.
Real-time dashboards.
Mobile and web application backends.
Analytics and semi-structured data.
Chat messages and notification systems.
However, MongoDB may not be the best first choice for every problem. Systems with highly relational data, complex joins, strict transactional reporting, or heavy SQL analytics may be better served by a relational database such as MySQL or PostgreSQL, or by using MongoDB together with other tools.
How These MongoDB Concepts Work Together
The topics in this article are connected. You start by understanding MongoDB's document model. Then you create or select a database. Inside that database, you work with collections. Inside collections, you insert documents. After inserting data, you use the Query API to find documents with filters, projections, sorting, and limits. When data changes, you update documents using update operators. When data is no longer needed, you delete it carefully or mark it as deleted using soft delete.
A professional MongoDB workflow usually looks like this:
Understand the application data and access patterns.
Choose database and collection names.
Design document structure using embedding and references wisely.
Create collections, validation rules, and indexes where needed.
Insert documents with correct data types.
Read documents using filters, projections, sorting, and pagination.
Update documents with safe update operators.
Delete documents carefully or use soft delete for important data.
Monitor query performance and improve indexes based on real usage.
Protect production data with authentication, backups, and limited permissions.
This workflow turns MongoDB from a simple document store into a reliable database layer for real applications.
Conclusion
MongoDB is a powerful document database that allows developers to store data in flexible, JSON-like documents. Its core concepts are simple to start with: databases contain collections, collections contain documents, and documents contain fields. But to use MongoDB well, developers must understand the Query API, document modeling, insert operations, find operations, update operators, delete safety, indexes, validation, and real project structure.
The topics covered in this article form the foundation of MongoDB development: MongoDB Home, Get Started, Query API, Create Database, Collection, Insert, Find, Update, and Delete. Once these topics are clear, the next step is to learn indexing, aggregation pipelines, schema validation, relationships, transactions, performance optimization, backup strategies, and security.
MongoDB is not only about storing JSON-like data. It is about designing documents around application behavior, reading data efficiently, updating it safely, and building systems that can grow without becoming chaotic. When used with discipline, MongoDB can be an excellent database choice for modern backend applications.

