PyMongo Basics

Explore the MongoDB driver for Python: PyMongo basics — MongoDB.

Focus: explore the mongodb driver for python: pymongo basics

Sponsored

You’ve mastered the MongoDB shell and maybe a few admin scripts, but now your application needs to talk to MongoDB directly — and that means choosing a driver. Without the right driver, you’re stuck hand-rolling HTTP requests to the MongoDB wire protocol, which is error-prone, insecure, and a total time sink. The official PyMongo driver solves this by giving you a clean, Pythonic API to connect, query, insert, and manage your MongoDB clusters — and this lesson walks you through the basics so you can go from zero to a working script in minutes.

The problem this lesson solves

MongoDB’s shell (mongosh) is great for ad-hoc queries, but your Python application can’t run db.collection.find() in a terminal at runtime. You need a programmatic way to:

  • Connect to a local or remote MongoDB instance
  • Insert, read, update, and delete documents
  • Handle authentication, connection pooling, and errors gracefully

Without a driver, you’d have to implement the MongoDB wire protocol from scratch — a huge, low-level task that’s also a security risk. PyMongo is the official, community-supported driver for Python, and it abstracts all that complexity. It’s what tools like Flask-MongoEngine and Django’s MongoDB backend use under the hood. This lesson gives you the essentials so you can write real code, not just theory.

Core concept / mental model

Think of PyMongo as a remote control for your MongoDB server. You don’t manipulate MongoDB directly; you send commands through a client that translates your Python calls into the wire protocol and back.

Here’s the key architecture in your head:

  1. MongoClient — the connection to the server. It manages a pool of connections, handles retries, and is your entry point.
  2. db = client.database_name — a proxy to a specific database. Doesn’t create it until you write data.
  3. collection = db.collection_name — a proxy to a collection. Same lazy behavior.
  4. collection.insert_one(document) — actually sends the operation and returns a result object.

A useful analogy: MongoClient is the power plug, the database is a room in your house, the collection is a drawer, and documents are the items you put inside. You don’t build the drawer until you put something in it — MongoDB creates collections and databases on first write.

Key definitions: - Document: a JSON-like structure (BSON) with field:value pairs. - _id: a unique primary key, auto-generated as an ObjectId if you don’t supply one. - Cursor: an iterable returned by find() — lazy, fetches batches as you iterate.

How it works step by step

Here’s the logical flow of a typical PyMongo script — follow this and you’ll avoid most common pitfalls:

  1. Install PyMongo with pip install pymongo (or pymongo[srv] for connection strings using the mongodb+srv:// scheme).
  2. Create a MongoClient with your connection string (e.g., mongodb://localhost:27017). The client is lazy — it doesn’t connect until an operation runs, so invalid URIs surface as errors only when you execute.
  3. Access a database via client['mydb'] or attribute style client.mydb.
  4. Access a collection similarly: db['users'] or db.users.
  5. Run a write operationinsert_one() / insert_many() to add data. MongoDB creates the DB/collection on first insert.
  6. Query with find_one() (returns a dict or None) or find() (returns a Cursor).
  7. Close the client when done (or use a with block for context management).

Cause and effect: if you try to access a database that doesn’t exist, you get a proxy object — no error. But if you call find_one() on it, you get None. If you insert, the DB and collection are created implicitly. This lazy model is elegant but can confuse beginners who expect eager validation.

Hands-on walkthrough

Let’s put it together with a complete example. First, install the driver (best practice: use a virtual environment):

python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate
pip install pymongo

Now, connect to a local MongoDB and perform CRUD operations:

from pymongo import MongoClient

# Connect (lazy — no network call until first op)
client = MongoClient('mongodb://localhost:27017')

# Access database and collection (still lazy)
db = client['shop']
products = db['products']

# Insert one document
result = products.insert_one({'name': 'Laptop', 'price': 1200, 'in_stock': True})
print(result.inserted_id)  # ObjectId('...')

# Insert many
many = products.insert_many([
    {'name': 'Mouse', 'price': 25, 'in_stock': True},
    {'name': 'Keyboard', 'price': 75, 'in_stock': False},
    {'name': 'Monitor', 'price': 300, 'in_stock': True},
])
print(len(many.inserted_ids))  # 3

# Find one
laptop = products.find_one({'name': 'Laptop'})
print(laptop)

# Find all in stock (cursor)
for doc in products.find({'in_stock': True}):
    print(doc['name'], doc['price'])

client.close()

Expected output (IDs will vary):

ObjectId('60f7b1c9d5c2d3a1b2c3d4e5')
3
{'_id': ObjectId('...'), 'name': 'Laptop', 'price': 1200, 'in_stock': True}
Laptop 1200
Mouse 25
Monitor 300

Notice: find_one() returns a single dict, while find() returns a Cursor that you iterate over. Also, the _id is auto-generated as an ObjectId — you can supply your own, but that’s rare.

Using a with block for cleaner resource management

PyMongo supports context managers to ensure the client is closed:

from pymongo import MongoClient

with MongoClient('mongodb://localhost:27017') as client:
    db = client.blog
    posts = db.posts
    posts.insert_one({'title': 'First post', 'views': 10})
    total = posts.count_documents({})
    print(f'Total posts: {total}')

Output:

Total posts: 1

Filtering with operators

PyMongo uses the same query operators as the shell — prefixed with $:

# Find products under $500
cheap = products.find({'price': {'$lt': 500}})
print([p['name'] for p in cheap])

# Count documents matching a filter
print(products.count_documents({'in_stock': True}))

Output:

['Mouse', 'Keyboard', 'Monitor']
3

Compare options / when to choose what

PyMongo isn’t the only way to talk to MongoDB from Python. Here’s a quick comparison:

Option Best for Key trade-off
PyMongo (low-level) Full control, custom queries, performance tuning Verbose, manual schema validation
MongoEngine (ODM) Modelling documents as classes, validation Adds abstraction, learning curve
Motor (Async) Asynchronous apps (FastAPI, aiohttp) Requires async/await syntax
Raw HTTP/REST Legacy systems, minimal dependencies Reimplements protocol, insecure

For most CRUD-heavy apps, PyMongo is the right start. If you need an object-document mapper, consider MongoEngine later. If you’re building an async app, Motor is built on PyMongo and shares most APIs — so learning PyMongo first pays off.

Pro tip: Always use pymongo[srv] if your connection string uses mongodb+srv:// (e.g., MongoDB Atlas). The standard install doesn’t include the DNS SRV resolution needed for that scheme.

Troubleshooting & edge cases

ServerSelectionTimeoutError

This is the most common error. Causes: server not running, wrong host/port, or unreachable network. Fix: ensure mongod is running, check the URI, and add serverSelectionTimeoutMS to fail faster:

client = MongoClient('mongodb://localhost:27017', serverSelectionTimeoutMS=2000)

InvalidURI

Malformed connection strings (e.g., missing scheme). Double-check for typos like missing // after mongodb:.

TypeError when passing wrong types

The driver is type-sensitive. For example, comparing a string to a numeric field won’t cast; use pymongo conversions or the right BSON types. If you need a concrete error, check that your queries use $lt numeric values, not strings.

Lazy connection surprises

Remember, MongoClient() doesn’t connect until an operation. If you instantiate but never run a query, you won’t know the server is down until the first operation — which might be much later. Use client.admin.command('ping') to test connectivity explicitly:

try:
    client.admin.command('ping')
    print('Connected')
except Exception as e:
    print('Connection failed:', e)

Indexes and performance

Common mistake: querying large collections without indexes. This isn’t a driver error, but it leads to slow find() operations. Create indexes with collection.create_index([('field', 1)]) for fields you filter on frequently.

What you learned & what's next

You’ve now got the core of PyMongo basics under your belt: you can install the driver, connect to a MongoDB instance, and perform CRUD operations with insert_one, find, and count_documents. You understand the lazy connection model, the difference between a document and a cursor, and how to troubleshoot the most common connection errors. You also saw how to compare PyMongo with alternatives like Motor and MongoEngine, so you can choose the right tool for your next project.

This is the foundation for building real applications. As a next step in this MongoDB track, you’ll dive into advanced querying — using aggregation pipelines, indexing strategies, and transactions. You’ll also learn how to handle data migrations and work with MongoDB Atlas in the cloud. Armed with these basics, you’re ready to move from scripting to production-grade code.

Pro tip: Keep your connection strings out of code. Use environment variables or a config file — never hardcode credentials, especially if you push to GitHub.

Practice recap

Practice by writing a small inventory script: connect to a local MongoDB, create a products collection, insert 5 items, then query all in-stock items with a price filter. Next, add error handling to catch connection failures and re-run to confirm your code is robust. This will turn the hands-on example into muscle memory.

Common mistakes

  • Forgetting to install pymongo[srv] when using a mongodb+srv:// connection string; you get an InvalidURI error instead of a clean install-time hint.
  • Assuming MongoClient() connects eagerly — it’s lazy, so a typo in the hostname doesn’t raise until the first operation, causing confusing timeouts later.
  • Iterating over aCursor twice expecting the same results; cursors are single-use — re-create the query instead.
  • Using string types for numeric comparisons (e.g., {'price': {'$lt': '500'}}) — the driver doesn’t coerce, so you get wrong results or no matches.

Variations

  1. Motor — the async wrapper around PyMongo; use await for non-blocking I/O in FastAPI or asyncio apps.
  2. MongoEngine — an ODM that maps documents to Python classes and adds validation, useful for complex domain models.
  3. PyMongo with pymongo[srv] — the standard variation since it enables Atlas and any mongodb+srv:// connection; keep it as your default install.

Real-world use cases

  • A Flask backend that stores user sessions in a sessions collection and retrieves them per request via find_one.
  • An ETL script that ingests logs into MongoDB using insert_many in batches, with a MongoClient connecting to a replication set.
  • A CI/CD pipeline that spins up a test MongoDB instance and runs integration tests with pymongo to validate data models.

Key takeaways

  • PyMongo is the official Python driver — install with pip install pymongo (add [srv] for Atlas).
  • The MongoClient is lazy: connections happen on first operation, so always test with ping() early.
  • Access databases and collections via client.db.collection — they’re created automatically on first insert.
  • Use insert_one/insert_many to write, find_one/find to read — find returns a single-use cursor.
  • Always check ServerSelectionTimeoutError and validate connection strings before debugging queries.
  • PyMongo is the foundation for ODM (MongoEngine) and async (Motor) layers — master it first.

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.