In the previous lesson, you successfully established a connection to the Firestore emulator and implemented a health check system that wrote and read a simple status document. That foundation taught you the essential patterns of creating a Firestore client, working with collections and documents, and using the set()
and get().to_dict()
methods for basic database operations. Now you're ready to build on that knowledge and tackle more realistic, structured data operations.
While the health check was perfect for verifying your connection, real applications need to work with complex, structured data that represent actual business entities. In this lesson, you'll learn to create and retrieve product documents that contain multiple fields with different data types, including strings, numbers, and categories. This represents a significant step forward from the simple {"status": "ok"}
document you worked with previously.
The goal of this lesson is to implement two essential functions: create_product()
and get_product()
. The first function will generate products with unique identifiers and structured data, while the second will safely retrieve products by their ID with proper error handling. These operations form the foundation of any product management system and demonstrate the core CRUD (Create, Read, Update, Delete) patterns you'll use throughout your NoSQL development career.
You'll also learn about generating unique document identifiers using Python's uuid
library, which is crucial for creating scalable systems where multiple users might be adding products simultaneously. By the end of this lesson, you'll have a working product management system that can create products with realistic data and retrieve them reliably, setting the stage for more advanced operations like updates and queries in future units.
Here's a quick refresher of what your health check implementation looked like:
This code taught you the essential patterns of creating a Firestore client, working with collections and documents, and using the set()
and get().to_dict()
methods for basic database operations. You wrote a simple {"status": "ok"}
document to a "health"
collection and then read it back to verify your connection.
In your health check example, you used a hardcoded document ID called "ping"
because you only needed one health status document. However, when working with products in a real application, you need each product to have its own unique identifier that will never conflict with other products, even when multiple users are creating products simultaneously.
Python's uuid
library provides an excellent solution for generating unique identifiers. You'll need to import this library alongside your existing Firestore imports. The uuid.uuid4().hex
method generates a random UUID (Universally Unique Identifier) and converts it to a hexadecimal string, which is perfect for use as a Firestore document ID.
Here's how your imports evolve from the previous lesson:
The uuid.uuid4()
function generates a random UUID using your system's random number generator, making it extremely unlikely that two calls will ever produce the same result. The .hex
method converts this UUID into a 32-character hexadecimal string like "a1b2c3d4e5f6789012345678901234ab"
. This format is ideal for Firestore document IDs because it's URL-safe, doesn't contain special characters, and provides excellent distribution for database performance.
Using UUIDs instead of sequential numbers or predictable patterns also provides security benefits. Users cannot guess other product IDs, and you don't need to maintain counters or worry about race conditions when multiple processes are creating products simultaneously. This approach scales naturally as your application grows and handles concurrent operations gracefully.
Building on your existing knowledge of the Firestore client and the set()
method from the health check lesson, you'll now create the create_product()
function that generates products with realistic, structured data. This function demonstrates how to work with more complex documents that contain multiple fields of different data types.
Notice how this code builds on patterns you learned in the previous lesson while introducing new concepts. You still use the familiar firestore.Client(project="demo-local")
pattern to create your database connection, but now you also define a constant COL = "books"
to store your collection name. This approach makes your code more maintainable because you can change the collection name in one place if needed.
The create_product()
function starts by creating a document reference using db.collection(COL).document(uuid.uuid4().hex)
. This combines the collection reference pattern you learned previously with the UUID generation technique. Each time this function runs, it creates a reference to a new, uniquely named document in the books collection.
The doc.set()
call uses the same method you learned in the health check lesson, but now with a more complex data structure. Instead of a simple status field, you're storing a complete book with a string title, a floating-point price, and a genre string. Firestore automatically handles the serialization of these different data types, storing them efficiently in the database.
The function returns doc.id
, which gives you the generated UUID string. This return value is crucial because it allows the calling code to reference this specific product later for retrieval, updates, or deletion operations.
While creating documents is straightforward, retrieving them safely requires more careful handling than the simple health check example. In real applications, you might attempt to retrieve products that don't exist, either because of user error, deleted products, or incorrect IDs. The get_product()
function demonstrates how to handle these scenarios gracefully.
This function builds on the get().to_dict()
pattern you learned in the health check lesson but adds crucial error handling. The function takes a pid
(product ID) parameter, which should be one of the UUID strings returned by create_product()
.
The db.collection(COL).document(pid).get()
call follows the same pattern as your health check, but instead of using a hardcoded document ID like "ping"
, it uses the provided pid
parameter. This returns a document snapshot, just like in the previous lesson, but now you need to handle the possibility that the document might not exist.
The key improvement is the snap.exists
check before calling to_dict()
. In your health check example, you could assume the document existed because you had just created it. However, when retrieving products by ID, especially IDs that might come from user input or external systems, you cannot make that assumption. The snap.exists
property returns True
if the document was found in the database and if it doesn't exist.
Now let's examine the complete solution that brings together all the concepts from this lesson and demonstrates how the create_product()
and get_product()
functions work together in a realistic workflow:
The main execution block demonstrates a typical create-then-retrieve workflow that you'll use frequently in product management applications. First, it calls create_product()
to generate a new product and stores the returned UUID in the pid
variable. Then it immediately retrieves that same product using get_product(pid)
to verify that the creation was successful.
When you run this code, you'll see output similar to this:
The first line shows the generated UUID that serves as the product's unique identifier. Your actual UUID will be different each time you run the program because uuid.uuid4()
generates random values. The second line shows the complete product data that was successfully stored and retrieved, demonstrating that the round-trip operation worked correctly.
This output confirms that your product was created with the correct structure, stored in the Firestore emulator, and retrieved successfully. The data types are preserved correctly — the title remains a string, the price stays as a floating-point number, and the genre is stored as a string. This demonstrates Firestore's ability to handle mixed data types within documents seamlessly.
You started with a simple health check that wrote and read a status document, then leveled up to creating and retrieving real product data with unique IDs and structured fields. You learned to use Python's uuid
library for unique document IDs, and how to safely fetch documents with existence checks. These patterns—creating, retrieving, and handling missing data—are the foundation for building robust NoSQL applications. Up next, you'll get hands-on practice and move on to updating and validating product data.
