Read and write data in Firestore with real-time listeners and one-time queries.
Firestore uses collections (like tables) and documents (like rows). Documents are JSON objects. Collections can contain subcollections. Think of it as a nested JSON tree.
import { db } from './firebase';
import {
collection, doc, getDocs, getDoc,
addDoc, setDoc, updateDoc, deleteDoc,
query, where, orderBy, limit,
onSnapshot
} from 'firebase/firestore';
// Add a document (auto-generated ID)
const docRef = await addDoc(collection(db, 'posts'), {
title: 'My Post',
body: 'Content',
userId: auth.currentUser.uid,
createdAt: new Date()
});
// Set a document (known ID)
await setDoc(doc(db, 'users', userId), { name: 'Alice', role: 'user' });
// Update specific fields
await updateDoc(doc(db, 'posts', postId), { title: 'New Title' });
// Delete
await deleteDoc(doc(db, 'posts', postId));// One-time query
const q = query(
collection(db, 'posts'),
where('userId', '==', currentUser.uid),
orderBy('createdAt', 'desc'),
limit(20)
);
const snapshot = await getDocs(q);
const posts = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
// Real-time listener (auto-updates when data changes)
const unsubscribe = onSnapshot(q, (snapshot) => {
const posts = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
setPosts(posts);
});
// Cleanup: call unsubscribe() when component unmountsaddDoc (auto ID) vs setDoc (known ID). Both create/overwrite.onSnapshot gives real-time updates. Returns an unsubscribe function — call it on cleanup.where, orderBy, limit. Composite queries need an index (Firebase prompts you).