Introduction: Working with Your Data

In the previous lesson, you learned how to create Firestore collections and add documents to them using the Python SDK. You now understand the fundamental structure of Firestore: data is organized into collections, which contain documents identified by unique document IDs. Each document can store various fields and data types, and you’ve seen how to organize related data using subcollections or references.

However, creating collections and adding documents is just the beginning. In real-world applications, you need to read individual documents, update them as your data changes, and sometimes remove documents that are no longer needed. You also need to retrieve multiple documents efficiently, whether you’re looking for all orders from a specific customer or searching for documents that meet certain criteria.

This lesson focuses on the complete lifecycle of working with data in Firestore. You’ll learn the four fundamental operations that every database supports: Create, Read, Update, and Delete — commonly known as CRUD operations. While you’ve already seen how to create documents, we’ll explore the other three operations in detail. More importantly, you’ll learn how to retrieve multiple documents using Firestore’s powerful querying capabilities. Understanding how to structure and execute queries is crucial for building efficient and scalable applications with Firestore.

By the end of this lesson, you’ll know how to implement all the essential data operations your applications will need and how to retrieve documents efficiently using Firestore’s query system.

Reading Data: Retrieving a Document by ID

After adding documents to your Firestore collection, the most common operation you’ll perform is reading individual documents back. In Firestore, each document is uniquely identified by its document ID within a collection. The fastest way to access a document is by referencing its ID directly.

Let’s start by creating a products collection, adding a product document, and then retrieving it. Here’s how you can read a single product using its document ID:

The read_product function demonstrates the essential pattern for retrieving documents. You obtain a reference to the document using its ID, then call .get() to fetch its data. If the document exists, you can access its fields using .to_dict(). If the document does not exist, .exists will be False, and you can return None to indicate that the document was not found. This approach prevents your code from crashing if the document is missing.

When you retrieve a document, Firestore returns all of its fields. In our product example, you’ll get back the name, price, , and fields. This operation is very fast because Firestore uses the document ID to locate the document directly.

Updating Data: Modifying Document Fields

As your application runs, you'll frequently need to modify existing documents. While you could use .set() to replace an entire document, this approach overwrites all fields. If you only want to change one or more fields, Firestore's .update() method allows you to modify specific fields without affecting the rest of the document.

Here's a function that updates a product's stock level:

The .update() method takes a dictionary of fields to update. In this example, we use firestore.Increment(quantity_change) to modify the stock field. This operation is atomic, meaning Firestore performs the entire update as a single, indivisible operation on the server. This is crucial when multiple users or processes might update the same document simultaneously.

We also update the last_updated field with the current timestamp. After updating, we fetch the document again to confirm the new values. Firestore's atomic operations, like Increment, make it easy to safely update numeric fields without needing to manually handle concurrent access.

To understand why atomic operations matter, consider what would happen without them. If you read a document's current stock (say, 50 units), subtract 5, and then write back 45, another process might do the same thing at the same time. Both processes would read 50, both would calculate 45, and you'd end up with 45 instead of 40 — losing one of the updates. With , Firestore handles the read-modify-write cycle on the server in a single atomic step, guaranteeing that both decrements are applied correctly.

Deleting Data: Removing a Document

Sometimes you need to remove documents from your collection entirely. Firestore’s .delete() method on a document reference removes the document and all its fields. This operation is straightforward and idempotent — deleting a document that does not exist has no effect and does not raise an error.

Here’s how you delete a product document:

The .delete() method requires only the document reference. After deleting a document, you can verify the deletion by trying to read it again — if the document no longer exists, .get().exists will be False.

Here’s an example of the complete lifecycle:

When you run this code, you’ll see output like:

Query: Efficient Data Retrieval

While reading a document by its ID is fast and direct, many applications need to retrieve multiple related documents. For example, you might want to get all orders placed by a specific customer or all products with low stock. Firestore supports powerful queries that allow you to retrieve documents matching specific field values.

Let's create an orders collection and demonstrate how to query for all orders from a specific customer:

The where method allows you to filter documents by field values. In this example, we retrieve all orders where the customer_id field matches the value we provide. Firestore queries are efficient because they use indexes to quickly find matching documents, even in large collections. When using the modern SDK, prefer where(filter=firestore.FieldFilter(...)) to avoid deprecation warnings.

The result of a query is a list of document snapshots, which you can convert to dictionaries using .to_dict().

Collection-wide Queries: Retrieving All Documents or Unfiltered Queries

In some cases, you may need to retrieve documents based on fields that are not indexed, or you may want to fetch all documents in a collection. In Firestore, all simple queries are indexed by default, and you can retrieve all documents in a collection by simply iterating over it. However, if you perform queries that require composite indexes and those indexes are not yet created, Firestore will prompt you to add them (you can create indexes from the Firebase Console or the Google Cloud Console under Firestore → Indexes).

Here’s how you can retrieve all orders above a certain total amount (an amount-only, collection-wide query):

Firestore queries like this are efficient because they use indexes. However, if you need to retrieve all documents in a collection without any filters, you can do so:

Keep in mind that reading large result sets can be slower and more expensive, as you pay for each document read. For very large collections, consider paginating your results.

Combining Query with Filters

Firestore queries become even more powerful when you combine multiple filters. You can chain multiple where clauses to narrow down your results based on several fields. For example, you might want to find all shipped orders for a specific customer:

Firestore supports compound queries, but there are some limitations. For example, you can only perform range comparisons (<, >, etc.) on a single field in a compound query, and you may need to create composite indexes for certain combinations of filters. If your query requires an index that does not exist, Firestore will provide a link to create it in the Firebase Console or in the Google Cloud Console (Firestore → Indexes).

Unlike some databases, Firestore does not require you to handle reserved words or use attribute name placeholders in queries.

Here’s an example of using these operations together:

Sample output:

Summary: Mastering Firestore Operations

You’ve now learned all the essential operations for working with data in Firestore. The CRUD operations — create, read, update, and delete — form the foundation of any database application. Use .set() to create new documents or replace existing ones. Use .get() to retrieve a document by its ID, which is the fastest way to access data. Use .update() to modify specific fields without affecting the rest of the document, and take advantage of atomic operations like Increment for safe concurrent updates. Use .delete() to remove documents, knowing that the operation is idempotent.

Firestore’s query system allows you to efficiently retrieve documents that match specific field values. Use where filters to find documents by field, and chain multiple filters for more complex queries. Firestore automatically uses indexes to make queries fast and scalable, but be aware of index requirements for compound queries. When you need to retrieve all documents in a collection, you can iterate over the collection, but be mindful of performance and cost for large datasets.

In the upcoming practice exercises, you’ll implement these operations yourself. You’ll create collections, perform CRUD operations, and experiment with Firestore’s query capabilities to see how they work in practice. Pay attention to how queries remain fast even as your collections grow, and try combining multiple filters to build flexible data retrieval patterns. These exercises will help you develop the intuition for choosing the right operation for each situation, a skill that’s essential for building production-ready applications with Firestore.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal