Firestore Database Basics
Firestore Database Basics
Firestore is Firebase's primary database. It's where your app stores and retrieves data. Let's understand how it works and learn the essential operations.
How Firestore Organizes Data
Firestore uses a collection → document structure:
Firestore Database
├── users (collection)
│ ├── user1 (document) → { name: "Alice", email: "alice@example.com" }
│ ├── user2 (document) → { name: "Bob", email: "bob@example.com" }
│ └── user3 (document) → { name: "Carol", email: "carol@example.com" }
├── posts (collection)
│ ├── post1 (document) → { title: "Hello World", author: "user1", ... }
│ └── post2 (document) → { title: "Firebase Guide", author: "user2", ... }
└── settings (collection)
└── appConfig (document) → { theme: "dark", version: "1.0" }
Key concepts:
- Collection — A group of documents (like a folder). Examples:
users,posts,products - Document — A single record with fields (like a file). Has a unique ID
- Fields — Key-value pairs inside a document (like
name: "Alice") - Document ID — A unique identifier. Can be auto-generated or custom
Firestore vs. SQL Databases
If you've heard of SQL databases (PostgreSQL, MySQL), here's how Firestore compares:
| SQL | Firestore |
|---|---|
| Table | Collection |
| Row | Document |
| Column | Field |
| Primary Key | Document ID |
What to ask your AI: "I'm designing a [type of app]. What collections and documents should I create in Firestore?"
Supported Data Types
Firestore documents can store these types:
| Type | Example | Notes |
|---|---|---|
| String | "Hello" | Text |
| Number | 42, 3.14 | Integers and decimals |
| Boolean | true, false | |
| Timestamp | Timestamp.now() | Date/time |
| Array | ["a", "b", "c"] | Ordered list |
| Map (Object) | { city: "NYC" } | Nested object |
| Null | null | Empty value |
| Reference | doc(db, "users/user1") | Link to another document |
| GeoPoint | new GeoPoint(lat, lng) | Location |
CRUD Operations
CRUD stands for Create, Read, Update, Delete — the four basic database operations. Let's learn each one.
Setup (Used in All Examples)
import { getFirestore, collection, doc, addDoc, getDoc, getDocs, updateDoc, deleteDoc, query, where, orderBy, Timestamp } from "firebase/firestore"; const db = getFirestore();
Create — Adding Documents
Add a document with an auto-generated ID:
// Firestore generates a unique ID for you const docRef = await addDoc(collection(db, "users"), { name: "Alice", email: "alice@example.com", role: "user", createdAt: Timestamp.now(), }); console.log("Document created with ID:", docRef.id);
Add a document with a custom ID:
import { setDoc } from "firebase/firestore"; // You choose the ID await setDoc(doc(db, "users", "alice123"), { name: "Alice", email: "alice@example.com", role: "user", createdAt: Timestamp.now(), });
When to use which:
- Auto ID (
addDoc) — Most of the time. IDs likexK7nR2pQ... - Custom ID (
setDoc) — When you need a predictable ID (e.g., using a user's auth UID)
Read — Getting Documents
Get a single document by ID:
const docSnap = await getDoc(doc(db, "users", "alice123")); if (docSnap.exists()) { console.log("User data:", docSnap.data()); // { name: "Alice", email: "alice@example.com", ... } } else { console.log("No such document!"); }
Get all documents in a collection:
const querySnapshot = await getDocs(collection(db, "users")); querySnapshot.forEach((doc) => { console.log(doc.id, "=>", doc.data()); });
Query with filters:
// Get users with role "admin" const q = query( collection(db, "users"), where("role", "==", "admin") ); const snapshot = await getDocs(q); // Get posts ordered by date, newest first const q2 = query( collection(db, "posts"), where("isPublished", "==", true), orderBy("createdAt", "desc") );
Common query operators: ==, !=, <, <=, >, >=, in, array-contains
What to ask your AI: "Write a Firestore query that gets all [items] where [condition], sorted by [field]."
Update — Modifying Documents
Update specific fields (without overwriting the entire document):
await updateDoc(doc(db, "users", "alice123"), { email: "newalice@example.com", updatedAt: Timestamp.now(), }); // Only email and updatedAt change — all other fields stay the same
Overwrite an entire document:
await setDoc(doc(db, "users", "alice123"), { name: "Alice Updated", email: "newalice@example.com", role: "admin", createdAt: Timestamp.now(), }); // ⚠️ This replaces the entire document — any fields not included are deleted
Delete — Removing Documents
await deleteDoc(doc(db, "users", "alice123"));
Note: Deleting a document does NOT delete its subcollections. If a user document has a subcollection of orders, those orders remain.
Real-Time Listeners
One of Firestore's superpowers is real-time updates. Instead of fetching data once, you can listen for changes:
import { onSnapshot } from "firebase/firestore"; // Listen to a single document const unsubscribe = onSnapshot(doc(db, "users", "alice123"), (doc) => { console.log("Current data:", doc.data()); // This runs EVERY TIME the document changes }); // Listen to a collection (or query) const unsubscribe2 = onSnapshot( query(collection(db, "messages"), orderBy("createdAt")), (snapshot) => { const messages = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); console.log("Messages:", messages); // This runs whenever any message is added, changed, or deleted } ); // Stop listening when you're done unsubscribe(); unsubscribe2();
Real-time listeners are perfect for chat apps, live dashboards, and collaborative features.
What to ask your AI: "Add a real-time listener that updates my React component whenever [collection] changes."
Structuring Your Data
Good data structure makes everything easier. Here are common patterns:
Flat Collections (Most Common)
users/
userId1 → { name, email, role }
userId2 → { name, email, role }
posts/
postId1 → { title, body, authorId, createdAt }
postId2 → { title, body, authorId, createdAt }
Reference other documents by storing their ID (e.g., authorId points to a user).
Subcollections
users/
userId1/
orders/
orderId1 → { total, items, status }
orderId2 → { total, items, status }
Use subcollections when data "belongs to" a parent document.
General Rules
- Keep documents small — Don't store huge arrays. If a list grows unbounded, use a subcollection instead
- Duplicate data when it speeds up reads — It's OK to store a user's name in a post document (denormalization)
- Think about your queries first — Structure data based on how you'll read it, not how it's logically related
What to ask your AI: "I'm building a [type of app] with [these features]. How should I structure my Firestore collections?"
Security Rules — Who Can Read/Write
Firestore Rules control access to your data. They live in firestore.rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Anyone can read published posts
match /posts/{postId} {
allow read: if resource.data.isPublished == true;
allow write: if request.auth != null;
}
// Users can only read/write their own data
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}
Key variables in rules:
request.auth— The currently logged-in user (null if not logged in)request.auth.uid— The user's IDresource.data— The existing document datarequest.resource.data— The incoming data (for writes)
What to ask your AI: "Write Firestore security rules for my app. Here's who should access what: [describe access patterns]."
What's Next?
You know how Firestore works! The next tutorial shows you how to connect Firebase to your React or Node.js app with proper configuration.
What to ask your AI: "Create a Firestore service file with TypeScript functions for CRUD operations on my [collection]."