Skip to content

Firebase Firestore & Realtime Database Guide: NoSQL Data Modeling

DodaTech Updated Jun 6, 2026 7 min read

Firebase offers two NoSQL databases — Cloud Firestore with flexible document model and Realtime Database with low-latency JSON tree sync for different use cases.

What You’ll Learn

  • Firestore document/collection data model vs Realtime Database JSON tree
  • CRUD operations: create, read, update, delete documents
  • Real-time listeners for live data sync
  • Subcollections, references, and data modeling patterns
  • When to choose Firestore vs Realtime Database

Why Firebase Database Matters

Traditional relational databases require schema migrations, JOINs, and connection management. Firebase databases are NoSQL and schema-less — you store JSON objects directly and query them with simple method calls. DodaTech’s Durga Antivirus Pro uses Firestore to store device configurations, scan results, and threat alerts, with real-time listeners updating the dashboard instantly when a new threat is detected.

    flowchart LR
    A["Firestore\nCollection/Doc Model"] -->|"CRUD"| B["Client App"]
    A -->|"Real-time Listener"| B
    C["Realtime DB\nJSON Tree"] -->|"CRUD"| B
    C -->|"Real-time Listener"| B
    A -->|"Sharded Timestamps"| D["Scalable\nCounters"]
    B -->|"Security Rules"| E["Firebase\nBackend"]
    style A fill:#dbeafe,stroke:#2563eb
    style C fill:#fef3c7,stroke:#d97706
  
Prerequisites: A Firebase project with Firestore enabled. Familiarity with Firebase Overview and JavaScript.

Firestore Data Model

Firestore organizes data into documents (individual records) stored in collections (groups of documents).

Collection: devices
  ├── Document: device-abc
  │   ├── name: "Office-PC"
  │   ├── os: "Windows 11"
  │   ├── lastScan: April 10, 2026
  │   └── userId: "user-123"
  └── Document: device-def
      ├── name: "Dev-Macbook"
      ├── os: "macOS 14"
      └── userId: "user-456"

Subcollection: devices/device-abc/threats
  ├── Document: threat-001
  │   ├── name: "Emotet"
  │   └── severity: "critical"

Key difference from SQL: There are no tables, no rows, no foreign keys, and no JOINs. Data that needs to be queried together should be stored together.

CRUD Operations in Firestore

Create a Document

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

// Option 1: setDoc — specify your own document ID
await setDoc(doc(db, "devices", "device-abc"), {
  name: "Office-PC",
  os: "Windows 11",
  userId: "user-123",
  lastScan: new Date()
});

// Option 2: addDoc — Firestore generates the ID
const docRef = await addDoc(collection(db, "threats"), {
  name: "Emotet",
  severity: "critical",
  detectedAt: new Date()
});
console.log("New threat ID:", docRef.id);

Which to use? Use setDoc when you have a meaningful ID (user ID, device serial number). Use addDoc for auto-generated IDs (threat reports, log entries).

Read a Document

import { doc, getDoc, getDocs, collection } from 'firebase/firestore';

// Read a single document
const docSnap = await getDoc(doc(db, "devices", "device-abc"));
if (docSnap.exists()) {
  console.log("Device data:", docSnap.data());
} else {
  console.log("No such device");
}

// Read all documents in a collection
const querySnapshot = await getDocs(collection(db, "devices"));
querySnapshot.forEach((doc) => {
  console.log(doc.id, "=>", doc.data());
});

Update a Document

import { updateDoc, doc } from 'firebase/firestore';

// Update specific fields (doesn't overwrite other fields)
await updateDoc(doc(db, "devices", "device-abc"), {
  lastScan: new Date(),
  status: "scanning"
});

// To delete a field, use deleteField()
import { deleteField } from 'firebase/firestore';
await updateDoc(doc(db, "devices", "device-abc"), {
  temporaryField: deleteField()
});

Delete a Document

import { deleteDoc, doc } from 'firebase/firestore';

await deleteDoc(doc(db, "devices", "device-abc"));
// Document is gone — its subcollections remain

Note: Deleting a document does NOT delete its subcollections. You must delete subcollections separately or during the parent delete.

Real-time Listeners

The biggest advantage of Firebase over traditional REST APIs is real-time sync. When data changes on the server, all connected clients update instantly:

import { onSnapshot, doc, collection } from 'firebase/firestore';

// Listen to a single document
const unsub = onSnapshot(doc(db, "devices", "device-abc"), (doc) => {
  if (doc.exists()) {
    console.log("Current data:", doc.data());
    updateDeviceUI(doc.data());
  }
});

// Listen to all threats (realtime updates)
const unsubAll = onSnapshot(collection(db, "threats"), (snapshot) => {
  snapshot.docChanges().forEach((change) => {
    if (change.type === "added") {
      console.log("New threat:", change.doc.data());
      showNotification(change.doc.data());
    }
    if (change.type === "modified") {
      console.log("Threat updated:", change.doc.data());
    }
    if (change.type === "removed") {
      console.log("Threat removed:", change.doc.id);
    }
  });
});

// When the component unmounts, unsubscribe
// unsub();

How it works: The SDK opens a WebSocket connection to Firestore. When data changes, Firestore pushes the update to all subscribed clients. No polling, no manual refresh.

Realtime Database

The Realtime Database uses a single JSON tree instead of collections/documents:

{
  "users": {
    "user-123": {
      "name": "Alice",
      "devices": {
        "device-abc": true
      }
    }
  },
  "devices": {
    "device-abc": {
      "name": "Office-PC",
      "lastScan": "2026-06-06"
    }
  }
}

When to use it: Realtime Database has lower latency than Firestore (milliseconds vs tens of milliseconds) and is simpler for shallow data. It’s ideal for chat apps, multiplayer games, and collaborative tools where every millisecond matters.

When to avoid it: Realtime Database struggles with complex queries, deep nesting, and massive scale. The 200MB depth limit and 100k concurrent connection cap make it unsuitable for large applications.

Common Mistakes

1. Nesting Data Too Deep

Firestore documents have a 1MB size limit. Storing an array of 10,000 threat IDs inside a device document will eventually exceed that limit. Store large arrays as subcollections.

2. Using Arrays for Lists That Need Real-time Updates

Arrays in Firestore don’t support partial updates well. When you add an item, you rewrite the entire array. Use maps (objects) or subcollections instead.

3. Not Using Batched Writes for Related Changes

When creating a user and their profile simultaneously, a batched write ensures both succeed or both fail:

const batch = writeBatch(db);
batch.set(doc(db, "users", uid), { name, email });
batch.set(doc(db, "profiles", uid), { bio, avatar });
await batch.commit();

4. Ignoring Firestore Security Rules

Without rules, anyone can read or write any data. Always set rules before deploying:

match /devices/{deviceId} {
  allow read, write: if request.auth != null 
    && request.auth.uid == resource.data.userId;
}

5. Reading All Documents When You Need One

Using getDocs(collection(db, "devices")) and filtering in JavaScript reads every device document. Use Firestore queries with where() to filter server-side.

Practice Questions

  1. What is the difference between a document and a collection in Firestore?
  2. When would you use setDoc vs addDoc?
  3. How do real-time listeners work under the hood?
  4. When should you choose Realtime Database over Firestore?
  5. Why doesn’t deleting a document delete its subcollections?

Answers:

  1. A document is a single record (like a JSON object). A collection is a group of documents (like a folder). Collections can contain subcollections.
  2. setDoc creates/overwrites a document at a specific path (you choose the ID). addDoc auto-generates a unique ID.
  3. The SDK opens a WebSocket connection. Firestore pushes data changes to all subscribed clients in real time — no polling needed.
  4. Realtime Database is better for low-latency, shallow data synchronization (chat, games). Firestore is better for complex queries, scalability, and structured data.
  5. Subcollections are independent of their parent document. Deleting the parent only removes that document node. You must iterate and delete subcollections separately.

Challenge: Design a Firestore data model for Durga Antivirus Pro’s scan history feature. Each user has devices, each device has scan history, and each scan has detected threats. Show the collection/document structure and write a batched write for creating a new scan with its threat detections.

FAQ

Can Firestore handle relational data?
: Firestore is a NoSQL document database — it doesn’t support JOINs or foreign keys. Model relational data by denormalizing (duplicating data) or using document references with client-side lookups.
What is the read/write limit for Firestore?
: Firestore supports up to 1 million concurrent connections and 10,000 writes/second per database. These limits scale automatically. For higher throughput, use sharding strategies.
How do I back up Firestore data?
: Use the gcloud firestore export command or the Firebase Console’s export feature. Schedule regular exports via Cloud Scheduler for production apps.
Is Firestore data encrypted?
: Yes — Firestore encrypts all data at rest and in transit using AES-256 and TLS 1.3. No additional configuration needed.

Try It Yourself

// Create a real-time device monitor
import { doc, onSnapshot } from 'firebase/firestore';

const deviceId = 'your-device-id';
const unsub = onSnapshot(doc(db, "devices", deviceId), (snap) => {
  const data = snap.data();
  if (data) {
    console.log(`Device: ${data.name} | OS: ${data.os} | Status: ${data.status || 'unknown'}`);
  }
});

// After 10 seconds, unsubscribe
setTimeout(() => {
  unsub();
  console.log("Stopped listening");
}, 10000);

What’s Next

TopicDescription
Firestore Queries & IndexingQuery, filter, sort data efficiently
Security Rules & HostingAccess control and app deployment
Firebase Auth GuideUser authentication for database access
SQL vs NoSQLCompare relational and document databases

What’s Next

Congratulations on completing this Firebase Database tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro