Skip to content

Cloud Firestore Guide: Document Model for Scalable NoSQL Data

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Cloud Firestore Guide: Document Model for Scalable NoSQL Data. We cover key concepts, practical examples, and best practices to help you master this topic.

Cloud Firestore is a flexible, scalable NoSQL document database that stores data in documents and collections with real-time sync, offline support, and automatic scaling.

What You'll Learn

How to model data in Firestore, perform CRUD operations, use real-time listeners, batch writes, transactions, and offline persistence — building a durable data layer without managing a database server.

Why It Matters

Firestore eliminates database server management, provides real-time sync out of the box, and scales automatically. DodaTech's Durga Antivirus Pro stores device configurations, scan history, and threat alerts in Firestore — serving 500K+ devices with zero database administration.

Real-World Use

A security dashboard that shows live threat data: each device writes scan results to Firestore, the dashboard listens for real-time updates, and administrators query historical data with filters and pagination.

flowchart LR
    A["Mobile App\nWrite Scan Result"] --> B["Firestore\nCollection"]
    B --> C["Real-time Listener\nDashboard"]
    B --> D["Cloud Functions\nProcess Alert"]
    B --> E["Analytics Query\nHistorical Data"]
    style B fill:#dbeafe,stroke:#2563eb
    style C fill:#bbf7d0,stroke:#16a34a

Understanding the Data Model

Firestore organizes data in a hierarchy: collections contain documents, and documents contain fields (key-value pairs). Documents can also contain subcollections.

Think of a collection as a folder and a document as a file with structured data. Unlike SQL tables, each document in a collection can have different fields — this is called schema flexibility.

// Document structure for a device scan record
const scanRecord = {
  deviceId: "dev_abc123",
  userId: "user_xyz789",
  scanDate: new Date("2026-06-28"),
  threatsFound: 3,
  status: "completed",
  threatList: [
    { name: "Trojan.Generic", severity: "high" },
    { name: "Adware.Bundle", severity: "low" }
  ]
};

Why this model? Firestore documents are JSON-like objects. Arrays, nested objects, and timestamps are native types. This matches how your application code structures data — no ORM needed.

Writing Documents

import { doc, setDoc, addDoc, collection } from "firebase/firestore";

// setDoc: Write with a known ID
async function writeWithKnownId() {
  const deviceRef = doc(db, "devices", "dev_abc123");
  await setDoc(deviceRef, {
    name: "Alice Laptop",
    os: "Windows 11",
    lastScan: new Date(),
    userId: "user_xyz789"
  });
  console.log("Device saved with ID: dev_abc123");
}
// Expected output: Device saved with ID: dev_abc123

// addDoc: Auto-generate ID
async function addScanRecord(deviceId) {
  const scansRef = collection(db, "devices", deviceId, "scans");
  const docRef = await addDoc(scansRef, {
    timestamp: new Date(),
    threats: 2,
    status: "clean"
  });
  console.log("Scan recorded with ID:", docRef.id);
}
// Expected output: Scan recorded with ID: ABC123xyz789

Reading Documents

import { getDoc, getDocs, collection, query, where } from "firebase/firestore";

// Get a single document
async function getDevice(deviceId) {
  const docSnap = await getDoc(doc(db, "devices", deviceId));
  if (docSnap.exists()) {
    console.log("Device data:", docSnap.data());
  } else {
    console.log("No device found");
  }
}
// Expected output: Device data: { name: "Alice Laptop", os: "Windows 11", ... }

// Query a collection
async function findDevicesByUser(userId) {
  const q = query(collection(db, "devices"), where("userId", "==", userId));
  const querySnapshot = await getDocs(q);
  querySnapshot.forEach((doc) => {
    console.log(doc.id, "=>", doc.data().name);
  });
}
// Expected output: dev_abc123 => Alice Laptop
//                  dev_def456 => Bob Desktop

Real-time Listener

import { onSnapshot, doc } from "firebase/firestore";

function listenToDevice(deviceId) {
  const deviceRef = doc(db, "devices", deviceId);
  const unsubscribe = onSnapshot(deviceRef, (doc) => {
    if (doc.exists()) {
      console.log("Device updated:", doc.data());
    }
  });
  // Call unsubscribe() when done listening
  return unsubscribe;
}
// Expected output (when data changes): Device updated: { name: "Alice Laptop", ... }

Batch Writes and Transactions

import { writeBatch, doc } from "firebase/firestore";

async function updateMultipleDevices() {
  const batch = writeBatch(db);
  batch.update(doc(db, "devices", "dev_abc123"), { status: "inactive" });
  batch.update(doc(db, "devices", "dev_def456"), { status: "inactive" });
  await batch.commit();
  console.log("Batch write completed — both devices updated atomically");
}
// Expected output: Batch write completed — both devices updated atomically

Offline Persistence

import { enableMultiTabIndexedDbPersistence } from "firebase/firestore";

async function enableOffline() {
  try {
    await enableMultiTabIndexedDbPersistence(db);
    console.log("Offline persistence enabled — data works without internet");
  } catch (err) {
    if (err.code === "failed-precondition") {
      console.log("Multiple tabs open — persistence in one tab only");
    }
  }
}
// Expected output: Offline persistence enabled — data works without internet

Common Mistakes

1. Creating Deeply Nested Data

Firestore documents have a 1 MiB size limit. Deeply nested objects make partial updates difficult and increase read costs. Flatten data or use subcollections.

2. Using Arrays for Large Lists

Arrays in documents have a 20 KiB limit and cannot be queried efficiently. Use subcollections for lists that grow beyond a few items.

3. Forgetting to Handle Document Existence

Always check docSnap.exists() before reading data. Accessing non-existent documents returns undefined and causes runtime errors.

4. Ignoring Write Limits

Firestore limits writes to 1 per second per document. Bulk updates to the same document cause contention. Use batch writes for multiple documents.

5. Not Using Subcollections for One-to-Many

Storing a list of scan IDs inside a device document causes growth issues. Use subcollections like devices/{id}/scans for one-to-many relationships.

Practice Questions

  1. What is the difference between setDoc and addDoc?
  2. When should you use a subcollection instead of a nested array?
  3. What happens when you write to a document faster than 1 write per second?
  4. How does offline persistence work with multi-tab support?

Answers:

  1. setDoc writes to a known document ID; addDoc auto-generates a unique ID.
  2. Use subcollections for one-to-many relationships that grow (scans, messages, comments). Nested arrays are for small, fixed-size lists.
  3. Writes beyond 1 per second cause contention errors. Use batch writes or retry logic.
  4. Each tab maintains its own cache. enableMultiTabIndexedDbPersistence synchronizes across tabs.

Challenge: Model Durga Antivirus Pro's data: a users/{id}/devices/{id}/scans/{id} hierarchy. Write a batch that records a new scan for all devices of a user after a mass threat detection.

FAQ

What is the difference between Firestore and Realtime Database?

Firestore uses document/collection model with rich queries, automatic scaling, and stronger consistency. Realtime Database uses a JSON tree with low-latency sync for simpler data.

How does Firestore billing work?

You pay for document reads, writes, deletes, and storage. Reads cost per document fetched, writes per document written. The free tier includes 50K reads, 20K writes, and 20K deletes per day.

Can I use Firestore with SQL queries?

No, Firestore is NoSQL. It supports equality filters, range filters, and composite indexes but not JOINs, aggregations, or full-text search.

How do I migrate data between Firestore projects?

Export using gcloud firestore export, import into the target project. For live migration, write a migration script using the Admin SDK.

What is the maximum document size in Firestore?

1 MiB per document. For larger data, use subcollections or Cloud Storage.

Does Firestore support transactions?

Yes, Firestore supports read/write transactions that operate on multiple documents atomically. Transactions retry automatically on contention.

Mini Project

Build a device management system with Firestore: create a devices collection, write scan results to a subcollection, set up a real-time listener for the dashboard, and test offline persistence by disconnecting the network.

What's Next

Firestore Queries & Indexes — filter, sort, and paginate your Firestore data efficiently.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro