Python MongoDB Tutorial

Store and query documents in MongoDB with PyMongo: connect, insert, find, update with operators, delete and index.

Lesson 70 of 107Advanced12 min read

MongoDB is a document database: instead of rows in tables, it stores documents, JSON-like records with named fields, grouped into collections. PyMongo is the official Python driver, and its API maps closely to Python dictionaries:

Terminal
python -m pip install pymongo
Python
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017")
observations = client["nature_demo"]["observations"]
observations.insert_one({"trail": "Willow", "birds": 4})
print(observations.find_one({"trail": "Willow"}))

This lesson covers document design, connecting with MongoClient, querying with filters, updating with operators such as $set, and working with _id values and indexes. The examples record wildlife observations along walking trails. You need a MongoDB server: a local installation, a Docker container (docker run -p 27017:27017 mongo) or a free MongoDB Atlas cluster. The MongoDB course covers the database itself in more depth.

Documents

A document is a set of field-value pairs, which PyMongo represents as a Python dict:

Python
observation = {
    "trail": "Willow",
    "date": "2026-05-14",
    "birds": 4,
    "species": ["wren", "robin"],
    "weather": {"wind": 12, "rain": False},
}

Fields can hold strings, numbers, booleans, None, lists, nested documents and dates (datetime objects). Documents in one collection don't have to share the same fields, which makes MongoDB flexible when records vary.

Flexible doesn't mean schema-free in practice. Decide which fields every document has, what types they hold and whether related data is embedded (species inside the observation) or referenced by id in another collection. Inconsistent documents, such as birds stored as 4 in one and "four" in another, make queries and reports unreliable. MongoDB can enforce a schema with JSON Schema validation on the collection.

Client lifecycle

MongoClient manages a pool of connections to the server. Create one client when your application starts and reuse it; creating a new client for every operation is slow and can exhaust server connections.

Python
import os
from pymongo import MongoClient

client = MongoClient(os.environ["MONGODB_URI"], serverSelectionTimeoutMS=5000)
db = client["nature_demo"]
observations = db["observations"]

The connection URI holds the host, port and credentials, for example mongodb+srv://user:password@cluster.example.net/. Keep it in an environment variable, never in code. serverSelectionTimeoutMS limits how long operations wait for a server before raising ServerSelectionTimeoutError; the default is 30 seconds.

Databases and collections are created lazily, when the first document is inserted. client.close() releases connections, and with MongoClient(...) as client: closes them automatically, which suits scripts.

Filters and updates

Queries take a filter document describing which documents to match:

Python
observations.find_one({"trail": "Willow"})                 # first match or None
for doc in observations.find({"birds": {"$gte": 5}}):      # a cursor over matches
    print(doc["trail"], doc["birds"])
observations.count_documents({"species": "wren"})          # matches array elements too

Comparison operators such as $gt, $gte, $lt, $in and $ne go inside the filter. find() accepts a projection to return only some fields, and the cursor supports .sort("birds", -1) and .limit(10).

Updates use update operators that describe the change:

Python
observations.update_one({"trail": "Willow"}, {"$set": {"birds": 6}})
observations.update_many({"trail": "Willow"}, {"$inc": {"visits": 1}})
observations.update_one({"trail": "Oak"}, {"$push": {"species": "jay"}})

$set changes the listed fields and leaves the others alone. replace_one() is different: it replaces the whole document with the new one, so any field you leave out is lost. Each update returns a result whose matched_count and modified_count tell you what happened. delete_one() and delete_many() remove documents matching a filter.

Identifiers and indexes

Every document has an _id field. If you don't supply one, the driver generates an ObjectId, and insert_one() returns it:

Python
from bson import ObjectId

result = observations.insert_one({"trail": "Willow", "birds": 4})
doc_id = result.inserted_id                       # an ObjectId
observations.find_one({"_id": doc_id})            # found
observations.find_one({"_id": str(doc_id)})       # None: a string isn't an ObjectId
observations.find_one({"_id": ObjectId(str(doc_id))})   # found again

Ids arriving from URLs or forms are strings, so convert them with ObjectId(text) (catching bson.errors.InvalidId for bad input) before querying.

Indexes make frequent queries fast, just as in SQL databases. Without one, MongoDB scans every document in the collection:

Python
observations.create_index("trail")
observations.create_index([("trail", 1), ("date", -1)])

Create indexes for the fields you filter and sort on most; each index speeds up reads but adds work to every write, so don't index everything.

Independent practice and a worked check

Task: install pymongo and work with a disposable observation collection using inserts, filters and updates.

Python
import os
from pymongo import MongoClient

with MongoClient(os.environ["MONGODB_URI"], serverSelectionTimeoutMS=5000) as client:
    col = client["practice_db"]["observations"]
    col.insert_many([{"trail": "Willow", "birds": 4}, {"trail": "Oak", "birds": 9}])
    col.update_one({"trail": "Willow"}, {"$set": {"birds": 5}})
    for doc in col.find({"birds": {"$gte": 5}}, {"_id": 0}).sort("trail"):
        print(doc)
    client.drop_database("practice_db")        # clean up

This prints {'trail': 'Oak', 'birds': 9} and {'trail': 'Willow', 'birds': 5}, then removes the practice database. For the worked check, the update operator that changes selected MongoDB fields is $set.

Reference and next practice

The PyMongo documentation includes a tutorial and API reference, and the MongoDB manual lists every query and update operator. Continue with database connections and the MongoDB course. Practise with the Python exercises and Python quiz.

Python MongoDB Tutorial Summary

  • Documents: MongoDB stores documents grouped into collections, and PyMongo is a Python driver.
  • Client lifecycle: MongoClient manages connections and should be configured with an appropriate URI and timeouts.
  • Filters and updates: Query filters select documents while update operators describe requested changes.
  • Identifiers and indexes: Inserted documents receive identifiers and indexes can support frequent queries.

Python MongoDB Tutorial Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Python MongoDB Tutorial in a small program

This needs a MongoDB server and MONGODB_URI set, for example to mongodb://localhost:27017. It prints Willow 4.

Python
import os
from pymongo import MongoClient

with MongoClient(os.environ["MONGODB_URI"], serverSelectionTimeoutMS=5000) as client:
    observations = client["nature_demo"]["observations"]
    result = observations.insert_one({"trail": "Willow", "birds": 4})
    record = observations.find_one({"_id": result.inserted_id})
    print(record["trail"], record["birds"])
Line-by-line explanation
  1. Line 4The with block closes the client's connections when it ends; the timeout avoids a long wait if the server is unreachable.
  2. Line 5Indexing the client gives a database, then a collection; both are created on first insert.
  3. Lines 6 to 7insert_one() returns the generated _id, which is then used to read the document back.
Example 2

Filters and update operators without a server

This uses plain Python to show what each update operator does to a document, so you can predict results before running them against MongoDB.

Python
import copy

doc = {"_id": 1, "trail": "Willow", "birds": 4, "species": ["wren"]}

def apply_update(document, update):
    result = copy.deepcopy(document)
    for field, value in update.get("$set", {}).items():
        result[field] = value
    for field, amount in update.get("$inc", {}).items():
        result[field] = result.get(field, 0) + amount
    for field, value in update.get("$push", {}).items():
        result.setdefault(field, []).append(value)
    return result

print(apply_update(doc, {"$set": {"birds": 6}}))
print(apply_update(doc, {"$inc": {"birds": 2, "visits": 1}}))
print(apply_update(doc, {"$push": {"species": "robin"}}))
replacement = {"trail": "Willow", "birds": 6}
print({"_id": doc["_id"], **replacement})
Output
{'_id': 1, 'trail': 'Willow', 'birds': 6, 'species': ['wren']}
{'_id': 1, 'trail': 'Willow', 'birds': 6, 'species': ['wren'], 'visits': 1}
{'_id': 1, 'trail': 'Willow', 'birds': 4, 'species': ['wren', 'robin']}
{'_id': 1, 'trail': 'Willow', 'birds': 6}
Line-by-line explanation
  1. Line 15$set changes only birds; species survives.
  2. Line 16$inc adds to existing numbers and creates missing fields starting from 0.
  3. Line 19A replacement keeps _id but drops every field not in the new document, including species.

Common Python MongoDB Tutorial Mistakes

Querying _id with a string

Don't
doc = observations.find_one({"_id": "665f1c2e9b1e8a3d4c5b6a71"})   # None
Do
from bson import ObjectId
doc = observations.find_one({"_id": ObjectId("665f1c2e9b1e8a3d4c5b6a71")})

Replacing a document when you meant to update one field

Don't
observations.replace_one({"trail": "Willow"}, {"birds": 6})   # trail and species are gone
Do
observations.update_one({"trail": "Willow"}, {"$set": {"birds": 6}})

Python MongoDB Tutorial in Real Projects

MongoDB with Python in practice

MongoDB suits data whose shape varies or nests naturally: product catalogues with different attributes per category, event logs, content management and user profiles. Python web applications use PyMongo directly, the async driver for asyncio frameworks, or object-document mappers such as Beanie and MongoEngine that add schemas and validation on top.

Production deployments use replica sets for availability, connection strings with TLS from a secrets store, indexes designed from real query patterns, and schema validation on important collections. Tests often run against a disposable database or a container, created and dropped for each test run.

Python MongoDB Tutorial FAQ

How do I connect to MongoDB from Python?

Install pymongo, then create a client with MongoClient("mongodb://localhost:27017") or your Atlas connection string. Access a database and collection by name, client["mydb"]["mycollection"], and call methods such as insert_one() and find().

What is the difference between update_one and replace_one?

update_one() applies operators such as $set and $inc to the matched document, keeping other fields. replace_one() swaps the whole document for a new one, keeping only _id, so omitted fields are deleted.

Why does find_one by _id return None?

Usually the _id is an ObjectId but you queried with a string. Convert it: find_one({"_id": ObjectId(id_text)}). Also check you're connected to the right database and collection names, since misspelt names silently refer to new, empty ones.

Is PyMongo the same as MongoDB?

No. MongoDB is the database server; PyMongo is the Python library that connects to it. Installing PyMongo doesn't install a server; you need a local MongoDB, a container or a hosted cluster.

Should I use MongoDB or a SQL database with Python?

Use a SQL database such as PostgreSQL or MySQL when data is highly relational and needs joins, strict schemas and multi-table transactions. MongoDB fits when records vary in shape, nest naturally and are mostly read and written as whole documents. Both work well from Python.

Python MongoDB Tutorial Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choiceeasy

Which update operator changes selected MongoDB fields?

Multiple choicemedium

Which review concern is valid for python mongodb tutorial?

Python MongoDB Tutorial Quiz

2 questions to check what you learned.

  1. Which update operator changes selected MongoDB fields?

    • Replacing every field manually
    • $set
    • Python ignores the distinction
  2. Which statement correctly describes filters and updates?

    • Query filters select documents while update operators describe requested changes.
    • Only the example output matters, not the rule
    • Every operation has the same behavior