Python MongoDB Tutorial: Learn NoSQL from Scratch (2026)
I switched from relational databases to MongoDB for a project where the schema changed weekly — startup life. MongoDB's document model let me add fields without migrations, and the Python driver (PyMongo) feels like working with native dicts and lists. The tradeoff is that you lose joins and transactions (pre-4.0), so data modeling requires a different mindset: embed or reference?
This tutorial covers everything you need to build a Python application with MongoDB: connecting, CRUD operations, indexing for performance, aggregation pipelines for reporting, and the decision framework for schema design. We'll build a simple blog backend as the example.
Connecting to MongoDB and Listing Databases
MongoClient connects to a running MongoDB instance. The connection string includes host, port, and optional authentication. PyMongo is lazy — it doesn't actually connect until the first operation. Use client.list_database_names() to verify the connection works.
from pymongo import MongoClient
client = MongoClient('mongodb://localhost:27017/')
db = client['blog_db']
print(client.list_database_names())
posts_collection = db['posts']
print(f"Connected to {db.name}")
CRUD Operations: Insert, Find, Update, Delete
Documents are Python dicts. insert_one() adds a single document and returns the inserted_id. find() returns a cursor iterable. update_one() uses $set to modify fields. delete_one() removes matching documents. The _id field is automatically a unique ObjectId if not provided.
post = {
'title': 'Getting Started with MongoDB',
'author': 'Alice',
'content': 'MongoDB is a document database...',
'tags': ['database', 'nosql'],
'views': 0
}
result = posts_collection.insert_one(post)
print(f"Inserted ID: {result.inserted_id}")
for doc in posts_collection.find({'author': 'Alice'}).limit(5):
print(doc['title'], doc['views'])
posts_collection.update_one(
{'_id': result.inserted_id},
{'$inc': {'views': 1}}
)
posts_collection.delete_one({'title': 'Spam Post'})
Query Operators and Projection
MongoDB supports rich query operators: $gt, $lt, $in, $regex, $exists. Projection limits which fields are returned — specify 1 for included fields, 0 for excluded. Sorting with sort() takes a list of (field, direction) tuples. I use projections heavily to reduce network transfer for large documents.
# Rich queries
popular = posts_collection.find({
'views': {'$gte': 1000},
'tags': {'$in': ['python', 'tutorial']}
}).sort([('views', -1)]).limit(10)
# Text search with regex
search_results = posts_collection.find({
'title': {'$regex': 'MongoDB', '$options': 'i'}
})
# Projection (only return title and views)
for post in posts_collection.find(
{'author': 'Alice'},
{'title': 1, 'views': 1, '_id': 0}
):
print(post)
Indexing for Query Performance
Without indexes, MongoDB scans every document (collection scan). create_index() on frequently queried fields dramatically speeds up queries. Compound indexes support queries that filter on multiple fields. Use explain() to check if your query uses an index.
# Single field index
posts_collection.create_index('author')
# Compound index
posts_collection.create_index([('author', 1), ('views', -1)])
# Text index for full-text search
posts_collection.create_index([('title', 'text'), ('content', 'text')])
# Check query execution
result = posts_collection.find({'author': 'Alice'}).explain()
print(result.get('executionStats', {}))
Aggregation Pipeline for Reporting
The aggregation pipeline processes documents through stages: $match (filter), $group (group by), $sort, $project (reshape), $unwind (explode arrays). It's more powerful than find() for computed results. I use it for generating reports and statistics that would require multiple queries in a relational database.
pipeline = [
{'$match': {'views': {'$gte': 100}}},
{'$group': {
'_id': '$author',
'total_views': {'$sum': '$views'},
'post_count': {'$sum': 1},
'avg_views': {'$avg': '$views'}
}},
{'$sort': {'total_views': -1}},
{'$project': {
'author': '$_id',
'total_views': 1,
'post_count': 1,
'avg_views': {'$round': ['$avg_views', 0]},
'_id': 0
}}
]
results = list(posts_collection.aggregate(pipeline))
for r in results:
print(f"{r['author']}: {r['post_count']} posts, {r['total_views']} views")
Schema Design: Embedding vs Referencing
MongoDB schema design is driven by access patterns. Embed related data when you always access it together (e.g., comments on a blog post). Reference when data is independent or grows unbounded (e.g., users and posts). The rule of thumb: data that changes independently should be referenced; data that's read together should be embedded.
# Embedding (comments inside post document)
post_with_comments = {
'title': 'My Post',
'content': '...',
'comments': [
{'user': 'Bob', 'text': 'Great post!', 'date': '2026-01-15'},
{'user': 'Charlie', 'text': 'Thanks for sharing', 'date': '2026-01-16'}
]
}
# Referencing (separate collections)
# posts collection: {"_id": ObjectId, "title": "...", "author_id": ObjectId}
# users collection: {"_id": ObjectId, "name": "Alice", "email": "..."}
# To get post with author:
post = posts_collection.find_one({'title': 'My Post'})
user = users_collection.find_one({'_id': post['author_id']})
Frequently Asked Questions
When should I use MongoDB instead of a relational database?
MongoDB excels when your schema evolves frequently, you have hierarchical or nested data, or you need horizontal scaling via sharding. Use a relational DB when you need complex joins, strict ACID transactions, or well-defined relationships.
How do I handle transactions in MongoDB?
MongoDB 4.0+ supports multi-document ACID transactions. Use session.start_transaction() and session.commit_transaction(). Note that transactions have performance overhead and should be used sparingly.
What is the difference between find() and aggregate()?
find() retrieves documents matching a filter with optional projection and sorting. aggregate() processes documents through a pipeline of stages for computed results, groupings, and transformations. Use find() for simple queries, aggregate() for complex transformations.
How do I monitor slow queries in MongoDB?
Enable the profiler: db.setProfilingLevel(1, {slowms: 100}). Query system.profile collection to see queries that exceed the threshold. Also check mongod logs for queries without indexes.
Originally published on Ayodhyyya. Last updated June 1, 2026.