Insert and Query Documents with PyMongo

Learn how to insert and query documents with PyMongo in MongoDB. This hands-on tutorial covers core concepts, step-by-step operations, troubleshooting, and what to study next.

Focus: insert and query documents with pymongo

Sponsored

You’ve installed MongoDB, launched a server, and maybe even connected PyMongo. But if you’ve ever tried to actually put data into a collection and then get it back out, you know the pain: documents that silently fail to save, queries that return nothing when you expect results, and a gnawing feeling that you’re not using the full power of the database. This lesson strips away the guesswork. You’ll learn exactly how to insert and query documents with PyMongo — the two operations that sit at the heart of every MongoDB application — and you’ll walk away with patterns you can use immediately.

The problem this lesson solves

Copying and pasting MongoDB shell commands from a blog post is easy. But when you move to Python, the shell syntax changes, error messages become cryptic, and suddenly db.collection.find() turns into collection.find({}) with a dictionary where you expected a JSON string. The core problem this lesson solves is the friction between MongoDB’s document model and Python’s data structures. You need to translate MongoDB’s query language into Python-friendly dictionaries, handle the differences between insert_one and insert_many, and write queries that actually return the documents you want — not just the ones that happen to work by accident.

Beyond the syntax, there’s a deeper issue: consistency and confidence. When you insert without checking the result, you can’t be sure your data landed. When you query with a vague find() call, you might retrieve more data than you need, slowing down your application. This lesson gives you the tools to insert and query documents with precision, verify every operation, and handle edge cases before they become production incidents.

Core concept / mental model

Think of a MongoDB collection as a smart filing cabinet where each drawer is a document, and each document is a JSON-like object that stores your data. You don’t need to predefine a schema — the cabinet accepts whatever you slide in, as long as it’s valid BSON. The moment you insert a document, MongoDB assigns it a unique _id field (if you don’t provide one), which acts like a barcode for retrieval.

When you query, you’re not scanning the entire cabinet; you’re asking MongoDB to find documents that match a filter. The filter is just a Python dictionary that describes the fields and values you’re looking for. For example, {"status": "active"} tells MongoDB: “Give me every document where the status field equals 'active'.” This mental model — documents as flexible records, queries as filters — is the foundation for everything you’ll do.

Key terms to remember:

  • Document: a single record, stored as a BSON object (binary JSON).
  • Collection: a group of documents, analogous to a table in SQL.
  • Filter: a dictionary that defines the criteria for matching documents.
  • Cursor: the result of a query — a lazy, iterable object that fetches documents as you loop through it.

How it works step by step

The process of inserting and querying documents with PyMongo follows a straight-line path. Let’s break it down:

Step 1: Connect to MongoDB

Before anything else, you need a client instance. The MongoDB server runs on localhost:27017 by default. You create a MongoClient and then select a database and collection.

from pymongo import MongoClient

client = MongoClient("mongodb://localhost:27017/")
db = client["shop"]  # or client.shop
products = db["products"]  # or db.products

Pro tip: client["shop"] and client.shop are equivalent. Use the bracket notation if your database name contains characters that aren’t valid Python identifiers.

Step 2: Insert documents

MongoDB gives you two primary insertion methods: insert_one for a single document and insert_many for a list of documents. Both return result objects that confirm what happened.

Step 3: Query documents

Use find_one to get the first matching document, or find to get a cursor for all matches. Filters use standard comparison operators like $gt, $lt, $in, and $regex.

Step 4: Process the results

Iterate over the cursor or access fields from a single document. Always handle the possibility that no document matches — find_one returns None in that case.

The cause-and-effect chain is simple: insert → document exists in the collection; query → MongoDB scans the collection (or uses an index) and returns matching documents.

Hands-on walkthrough

Now let’s put that theory into practice. Below are complete, runnable examples. I assume you have MongoDB running locally and PyMongo installed (pip install pymongo).

Example 1: Insert a single document and verify

from pymongo import MongoClient

client = MongoClient("mongodb://localhost:27017/")
db = client["shop"]
products = db["products"]

product = {
    "name": "Laptop",
    "price": 999.99,
    "in_stock": True,
    "tags": ["electronics", "computers"]
}

result = products.insert_one(product)
print("Inserted ID:", result.inserted_id)

# Verify the insert
query = {"_id": result.inserted_id}
doc = products.find_one(query)
print("Found document:", doc)

Expected output:

Inserted ID: 65f2a1b4c9e3f5a7d8b0c1d2
Found document: {'_id': ObjectId('65f2a1b4c9e3f5a7d8b0c1d2'), 'name': 'Laptop', 'price': 999.99, 'in_stock': True, 'tags': ['electronics', 'computers']}

Notice that the _id was auto-generated. If you want to control the _id, just include it in the document — MongoDB will honor your value.

Example 2: Insert multiple documents and query with filters

from pymongo import MongoClient

client = MongoClient("mongodb://localhost:27017/")
db = client["shop"]
products = db["products"]

# Insert several products at once
many_products = [
    {"name": "Mouse", "price": 29.99, "in_stock": False, "tags": ["electronics"]},
    {"name": "Keyboard", "price": 89.99, "in_stock": True, "tags": ["electronics"]},
    {"name": "Desk", "price": 199.99, "in_stock": True, "tags": ["furniture"]},
]
result = products.insert_many(many_products)
print("Inserted IDs:", result.inserted_ids)

# Query all in-stock products
in_stock = list(products.find({"in_stock": True}))
print("In-stock products:")
for prod in in_stock:
    print(f"- {prod['name']} at ${prod['price']}")

# Query products under $100
cheap = list(products.find({"price": {"$lt": 100}}))
print("Products under $100:")
for prod in cheap:
    print(f"- {prod['name']} at ${prod['price']}")

Expected output:

Inserted IDs: [ObjectId('65f2a1b4c9e3f5a7d8b0c1d3'), ObjectId('65f2a1b4c9e3f5a7d8b0c1d4'), ObjectId('65f2a1b4c9e3f5a7d8b0c1d5')]
In-stock products:
- Laptop at $999.99
- Keyboard at $89.99
- Desk at $199.99
Products under $100:
- Mouse at $29.99
- Keyboard at $89.99

Example 3: More advanced queries with operators and projections

from pymongo import MongoClient

client = MongoClient("mongodb://localhost:27017/")
db = client["shop"]
products = db["products"]

# Products that are either in the 'electronics' tag OR have a price >= 500
query = {
    "$or": [
        {"tags": "electronics"},
        {"price": {"$gte": 500}}
    ]
}
result = list(products.find(query))
print("Matches electronics or expensive:")
for prod in result:
    print(f"- {prod['name']} (${prod['price']})")

# Project only the 'name' and 'price' fields, exclude _id
projection = {"_id": 0, "name": 1, "price": 1}
names = list(products.find({}, projection))
print("All products (name & price):")
for prod in names:
    print(f"- {prod}")

Expected output (will vary based on your existing data):

Matches electronics or expensive:
- Laptop ($999.99)
- Mouse ($29.99)
- Keyboard ($89.99)
- Desk ($199.99)
All products (name & price):
- {'name': 'Laptop', 'price': 999.99}
- {'name': 'Mouse', 'price': 29.99}
- {'name': 'Keyboard', 'price': 89.99}
- {'name': 'Desk', 'price': 199.99}

Pro tip: Use projections to reduce network transfer and memory usage. Only fetch the fields you actually need.

Compare options / when to choose what

When inserting documents, you have two main methods; when querying, you have multiple operators and tools. Here’s a comparison to help you decide.

Insertion: insert_one vs insert_many

Method When to use Performance & behavior
insert_one Single document, need to reuse the result ID immediately One round-trip to server, returns inserted_id
insert_many Batch inserts, e.g., initial data load or log ingestion Single round-trip for many documents, returns list of IDs

Query operators: $eq, $gt, $in, $regex

Operator Use case Example query
$eq Exact match (default if no operator) {"name": "Laptop"}
$gt/$lt Range comparisons for numbers/dates {"price": {"$gt": 100}}
$in Match any value in a list {"tags": {"$in": ["electronics", "furniture"]}}
$regex Pattern matching on strings {"name": {"$regex": "^L"}}

Query methods: find_one vs find

Method Returns When to use
find_one Single document or None Get specific record by ID, check existence
find Cursor (iterable) Get all matching documents, often with filters

Insert vs Update

Inserting always creates a new document. If you need to modify an existing one, use update_one or replace_one. Inserting a document with an _id that already exists will raise a DuplicateKeyError.

Troubleshooting & edge cases

Even with the right approach, things can go wrong. Here are the most common pitfalls and how to fix them.

Error: pymongo.errors.PyMongoError: connection refused

Cause: MongoDB server isn’t running, or the connection string is wrong. Fix: Start MongoDB (mongod) and check the port. Use ps aux | grep mongod on Linux/macOS or Task Manager on Windows.

Error: DuplicateKeyError on insert

Cause: You tried to insert a document with an _id that already exists. Fix: Check if the _id is supposed to be unique. If you need upsert behavior, use update_one with upsert=True.

Empty result when you expect documents

Cause: The filter doesn’t match any document, or you’re querying a different collection/database. Fix: Verify your collection name and database name. Use collection.find_one({}) to see if any data exists. Print your filter to ensure types match (e.g., "price" as a string vs. number).

Type mismatches

Cause: Querying with a string where the field stores a number, or vice versa. Example: {"price": "100"} won’t match price: 100. Fix: Convert your variable to the correct type before inserting or querying.

Slow queries with many documents

Cause: Missing indexes on frequently queried fields. Fix: Create an index on the filter fields, e.g., products.create_index("name"). This is essential for production performance.

find returns a cursor, not a list

Beginners often expect find to return a list. Remember, find is lazy — you must iterate over it, or convert to a list with list(). If you forget, you’ll get a Cursor object, which can be confusing.

What you learned & what's next

You now know how to insert and query documents with PyMongo — from connecting to a database, inserting single or multiple documents, to querying with filters, operators, and projections. You’ve also learned how to troubleshoot common issues like connection errors, duplicate keys, and type mismatches. These skills are the bedrock of any MongoDB application, whether you’re building a simple script or a full-scale web service.

What’s next? Now that you can get data in and out, you should move on to updating and deleting documents — the natural next step in your MongoDB journey. You’ll learn how to modify existing documents with update_one, update_many, and replace_one, and how to remove them with delete_one and delete_many. Mastering the full CRUD cycle will make you confident in handling any data operation with PyMongo.

Practice recap

Now it's your turn: create a new collection called students and insert five student documents with fields like name, age, grade, and subjects (a list). Practice querying all students above a certain age, students in a specific subject, and use a projection to show only names. Try inserting a duplicate _id to see the error, then fix it with an update.

Common mistakes

  • Querying with a string instead of a number (e.g., {"price": "100"}) when the field stores an integer — this silently returns no matches.
  • Forgetting to convert a Cursor to a list — calling print(cursor) shows pymongo.cursor.Cursor instead of your documents.
  • Assuming insert_many is atomic — if one document has an invalid field, the entire operation fails unless you set ordered=False.
  • Using find_one without checking for None — accessing fields on a None result raises TypeError.
  • Ignoring indexes — querying large collections without indexes leads to slow performance and timeouts.

Variations

  1. Use Motor (async PyMongo) for asynchronous web frameworks like FastAPI or Tornado.
  2. Use bulk_write for complex mixed operations (insert, update, delete) in a single batch.
  3. Use the MongoDB Aggregation Pipeline (aggregate()) instead of multiple queries when you need to transform or join data.

Real-world use cases

  • E-commerce catalog: insert thousands of products with insert_many, then query by category and price range for storefront displays.
  • IoT sensor data: append time-series readings as documents, then query recent data with $gt on timestamps for dashboards.
  • User activity logs: store each event as a document, filter by user ID and date range for analytics and audit trails.

Key takeaways

  • Connect with MongoClient and use bracket or attribute notation to access databases and collections.
  • Use insert_one for single documents and insert_many for batches; always check the inserted_id(s) to confirm success.
  • Query with find_one to get a single document and find to get a cursor; always iterate or convert to a list.
  • Master comparison operators like $gt, $lt, $in, and $regex to build precise filters.
  • Use projections to retrieve only the fields you need, improving performance and reducing bandwidth.
  • Troubleshoot by checking connection strings, verifying types in filters, and creating indexes for speed.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.