GCP Cloud Firestore
Cloud Firestore is GCP's fully managed, serverless NoSQL document database. It stores data as documents organized into collections, similar to how a filing cabinet holds folders (collections) that contain individual files (documents). Firestore is designed for mobile, web, and server applications that need real-time data synchronization and offline support.
Unlike relational databases that store data in rigid rows and tables, Firestore stores data as flexible JSON-like documents. Each document can have different fields — perfect for applications where data structures change frequently, like user profiles, product catalogs, or chat messages.
Firestore vs Cloud SQL
| Feature | Cloud SQL (Relational) | Firestore (NoSQL) |
|---|---|---|
| Data model | Tables with fixed rows and columns | Documents inside collections |
| Schema | Strict — all rows follow the same structure | Flexible — each document can differ |
| Scaling | Vertical (bigger server) | Automatic horizontal scaling |
| Real-time sync | No | Yes — client receives updates instantly |
| Offline support | No | Yes — works without internet connection |
| Best for | Financial data, complex joins | Mobile apps, real-time dashboards |
Firestore Data Model
Database
└── Collection: "users"
├── Document: "user_001"
│ ├── name: "Rahul Sharma"
│ ├── email: "rahul@example.com"
│ ├── age: 28
│ └── Subcollection: "orders"
│ └── Document: "order_A1"
│ ├── product: "Laptop"
│ ├── price: 49999
│ └── status: "delivered"
└── Document: "user_002"
├── name: "Priya Patel"
├── email: "priya@example.com"
└── location: { city: "Mumbai", state: "Maharashtra" }
Each document can contain strings, numbers, booleans, arrays, maps (nested objects), timestamps, references to other documents, and even subcollections.
Reading and Writing Data
Python – Adding a Document
from google.cloud import firestore
db = firestore.Client()
# Add a document with an auto-generated ID
doc_ref = db.collection("users").add({
"name": "Rahul Sharma",
"email": "rahul@example.com",
"age": 28
})
print(f"Document added with ID: {doc_ref[1].id}")
Python – Setting a Document with a Specific ID
db.collection("users").document("user_001").set({
"name": "Priya Patel",
"email": "priya@example.com",
"city": "Mumbai"
})
Python – Reading a Document
doc = db.collection("users").document("user_001").get()
if doc.exists:
print(doc.to_dict())
else:
print("Document not found.")
Python – Updating Specific Fields
db.collection("users").document("user_001").update({
"age": 29,
"city": "Delhi"
})
Python – Deleting a Document
db.collection("users").document("user_001").delete()
Querying Firestore
# Get all users in Mumbai
users_ref = db.collection("users")
query = users_ref.where("city", "==", "Mumbai")
docs = query.stream()
for doc in docs:
print(doc.id, doc.to_dict())
# Compound query — city AND age
query = users_ref.where("city", "==", "Mumbai").where("age", ">=", 25)
# Order and limit results
query = users_ref.order_by("name").limit(10)
Note: Compound queries (multiple where clauses on different fields) require a composite index. Firestore will show an error with a direct link to create the index if one is missing.
Real-Time Listeners
Firestore's most powerful feature is real-time synchronization. Applications can subscribe to changes in a document or collection and receive updates instantly — without polling.
Real-Time Flow:
App A updates document "order_001" → status: "shipped"
│
▼
Firestore detects the change
│
▼
App B (subscribed to "order_001") receives update automatically
→ UI updates instantly showing "Shipped"
# Python real-time listener
def on_snapshot(doc_snapshot, changes, read_time):
for doc in doc_snapshot:
print(f"Update received: {doc.to_dict()}")
doc_ref = db.collection("orders").document("order_001")
doc_watch = doc_ref.on_snapshot(on_snapshot)
Transactions and Batched Writes
Transaction
A transaction reads and writes documents atomically. Either all operations succeed, or none do — preventing partial updates.
# Transfer credits between two users atomically
@firestore.transactional
def transfer_credits(transaction, from_ref, to_ref, amount):
from_doc = from_ref.get(transaction=transaction)
to_doc = to_ref.get(transaction=transaction)
new_from_balance = from_doc.get("credits") - amount
new_to_balance = to_doc.get("credits") + amount
transaction.update(from_ref, {"credits": new_from_balance})
transaction.update(to_ref, {"credits": new_to_balance})
transaction = db.transaction()
transfer_credits(transaction, db.document("users/u1"), db.document("users/u2"), 100)
Batched Write
A batch groups multiple write operations (set, update, delete) into one atomic operation. Up to 500 documents can be written in a single batch.
batch = db.batch()
batch.set(db.collection("users").document("u3"), {"name": "Ali", "city": "Pune"})
batch.update(db.collection("users").document("u1"), {"city": "Bengaluru"})
batch.delete(db.collection("users").document("u0"))
batch.commit() # All three operations happen together
Firestore Security Rules
For mobile and web apps that connect to Firestore directly (without a backend server), Security Rules control who can read and write documents.
// Allow only authenticated users to read their own data
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null
&& request.auth.uid == userId;
}
}
}
Key Takeaways
- Firestore is a serverless NoSQL document database with automatic scaling.
- Data is stored as documents inside collections — flexible, schema-free structures.
- Real-time listeners push data changes to connected clients instantly.
- Transactions ensure atomic reads and writes — all or nothing.
- Batched writes group up to 500 document operations into one atomic operation.
- Security Rules protect data when mobile/web apps connect directly to Firestore.
