Firestore Read Write
Reading and writing data in Firestore covers the four core operations every app needs: creating documents, reading documents, updating fields, and deleting documents. Firebase provides specific functions for each operation. This topic covers all four in detail with practical examples.
Setting Up Firestore in Code
Initialize Firestore and export the database instance from your Firebase setup file:
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
const firebaseConfig = { /* your config */ };
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
Writing Data — Creating a Document
Two functions create or overwrite documents: setDoc and addDoc.
setDoc — You Choose the ID
import { doc, setDoc } from "firebase/firestore";
import { db } from "./firebase";
// Create a user document using the user's UID as the document ID
await setDoc(doc(db, "users", "uid_alice"), {
name: "Alice",
email: "alice@example.com",
age: 28,
createdAt: new Date()
});
If a document with that ID already exists, setDoc overwrites it entirely. Use setDoc when you know the document ID in advance — like using a user's UID as their profile document ID.
addDoc — Firestore Generates the ID
import { collection, addDoc } from "firebase/firestore";
import { db } from "./firebase";
// Create a new post — Firestore assigns a random ID
const docRef = await addDoc(collection(db, "posts"), {
title: "My First Post",
body: "Firestore is easy to use.",
authorId: "uid_alice",
publishedAt: new Date()
});
console.log("Post created with ID:", docRef.id);
Use addDoc when you do not care what the ID is — like creating posts, orders, or messages where you just need each item to have a unique identifier.
Reading a Single Document
import { doc, getDoc } from "firebase/firestore";
import { db } from "./firebase";
const userRef = doc(db, "users", "uid_alice");
const snapshot = await getDoc(userRef);
if (snapshot.exists()) {
const data = snapshot.data();
console.log("User name:", data.name);
console.log("User email:", data.email);
} else {
console.log("Document does not exist.");
}
The snapshot has two key methods:
snapshot.exists()— returnstrueif the document was foundsnapshot.data()— returns the document's fields as a JavaScript object
The snapshot also contains snapshot.id — the document's ID — which is useful when you need to reference the document later.
Reading All Documents in a Collection
import { collection, getDocs } from "firebase/firestore";
import { db } from "./firebase";
const postsRef = collection(db, "posts");
const querySnapshot = await getDocs(postsRef);
querySnapshot.forEach((doc) => {
console.log("Post ID:", doc.id);
console.log("Post title:", doc.data().title);
});
// Or convert to an array:
const posts = querySnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data()
}));
Updating Specific Fields
Use updateDoc to change specific fields without overwriting the whole document. Fields not mentioned in the update stay unchanged.
import { doc, updateDoc } from "firebase/firestore";
import { db } from "./firebase";
await updateDoc(doc(db, "users", "uid_alice"), {
age: 29,
city: "Mumbai" // adds a new field if it doesn't exist
});
// name and email stay unchanged
Updating Nested Fields
To update a field inside a nested map without overwriting the whole map, use dot notation in the field path:
await updateDoc(doc(db, "users", "uid_alice"), {
"address.city": "Pune", // only updates city inside address map
"address.zip": "411001"
// address.street stays unchanged
});
Using FieldValue for Special Updates
Firestore provides special update operations through FieldValue for common data manipulation tasks:
Incrementing a Number
import { doc, updateDoc, increment } from "firebase/firestore";
// Increase likesCount by 1 — safe for concurrent updates
await updateDoc(doc(db, "posts", "post_001"), {
likesCount: increment(1)
});
Adding to an Array
import { doc, updateDoc, arrayUnion, arrayRemove } from "firebase/firestore";
// Add a tag without duplicating it
await updateDoc(doc(db, "posts", "post_001"), {
tags: arrayUnion("firebase")
});
// Remove a tag
await updateDoc(doc(db, "posts", "post_001"), {
tags: arrayRemove("old-tag")
});
Server Timestamp
import { doc, updateDoc, serverTimestamp } from "firebase/firestore";
// Set updatedAt to the server's current time
await updateDoc(doc(db, "posts", "post_001"), {
updatedAt: serverTimestamp()
});
Using serverTimestamp() is better than new Date() because it uses Firestore's server clock, not the user's device clock (which may be set incorrectly).
Deleting a Document
import { doc, deleteDoc } from "firebase/firestore";
import { db } from "./firebase";
await deleteDoc(doc(db, "posts", "post_001"));
console.log("Post deleted.");
Deleting a Specific Field
import { doc, updateDoc, deleteField } from "firebase/firestore";
// Remove the "draft" field from a document
await updateDoc(doc(db, "posts", "post_001"), {
draft: deleteField()
});
The Difference Between setDoc and updateDoc
Existing document: { name: "Alice", age: 28, city: "Mumbai" }
After setDoc(ref, { name: "Alice", age: 29 }):
Result: { name: "Alice", age: 29 }
-- city is GONE (setDoc replaces everything)
After updateDoc(ref, { age: 29 }):
Result: { name: "Alice", age: 29, city: "Mumbai" }
-- city is preserved (updateDoc merges)
To get setDoc to merge instead of replace, pass a merge option:
import { setDoc, merge } from "firebase/firestore";
await setDoc(doc(db, "users", "uid_alice"), { age: 29 }, { merge: true });
// Behaves like updateDoc — only changes age
Key Takeaway
Firestore write operations break into two categories: setDoc and addDoc for creating documents, and updateDoc for changing specific fields. Read operations use getDoc for a single document and getDocs for a collection. Delete documents with deleteDoc and specific fields with deleteField. Use serverTimestamp, increment, arrayUnion, and arrayRemove for safe, atomic field updates.
