<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ firestore - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ firestore - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 24 Jul 2026 20:08:27 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/firestore/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Firestore Data Modeling Guide: Embedded Documents vs Referencing (with a Blog Case Study) ]]>
                </title>
                <description>
                    <![CDATA[ When developers transition from the relational world (MySQL, PostgreSQL) to Firestore, Firebase's NoSQL document database, they often bring their old habits with them. They try to replicate tables, fo ]]>
                </description>
                <link>https://www.freecodecamp.org/news/firestore-data-modeling-guide-embedded-documents-vs-referencing-with-a-blog-case-study/</link>
                <guid isPermaLink="false">6a63826ed2f5d140f2aaa325</guid>
                
                    <category>
                        <![CDATA[ firestore ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ NoSQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Databases ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Query ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SQL ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Caleb Mintoumba ]]>
                </dc:creator>
                <pubDate>Fri, 24 Jul 2026 15:19:10 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/f0166ca3-ca48-45f6-bb2f-ee6b20701ea0.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When developers transition from the relational world (MySQL, PostgreSQL) to Firestore, Firebase's NoSQL document database, they often bring their old habits with them. They try to replicate tables, foreign keys, and joins.</p>
<p>The result? Complex queries, skyrocketing read costs, and a database structure that becomes a nightmare to maintain after just a few features.</p>
<p>To understand how Firestore works, we first need to look at our point of comparison: the relational model. Once we map out how SQL does things, we can see exactly where Firestore diverges, and how to structure NoSQL data correctly.</p>
<p>In this guide, we'll cover NoSQL design principles, embedding vs. referencing, and relationship modeling (1-1, 1-N, N-N). We'll also walk through a concrete blog case study.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-the-relational-mindset-how-sql-handles-data">The Relational Mindset: How SQL Handles Data</a></p>
</li>
<li><p><a href="#heading-the-firestore-paradigm-nosql-with-relationships">The Firestore Paradigm: NoSQL with Relationships</a></p>
</li>
<li><p><a href="#heading-the-core-building-blocks-documents-and-collections">The Core Building Blocks: Documents and Collections</a></p>
</li>
<li><p><a href="#heading-the-golden-rule-model-for-reads-not-writes">The Golden Rule: Model for Reads, Not Writes</a></p>
</li>
<li><p><a href="#heading-embedding-vs-referencing-denormalization">Embedding vs. Referencing (Denormalization)</a></p>
</li>
<li><p><a href="#heading-how-to-model-relationships-1-1-1-n-n-n">How to Model Relationships (1-1, 1-N, N-N)</a></p>
</li>
<li><p><a href="#heading-best-practices-and-pitfalls-to-avoid">Best Practices and Pitfalls to Avoid</a></p>
</li>
<li><p><a href="#heading-case-study-designing-a-scalable-blog-database">Case Study: Designing a Scalable Blog Database</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>This guide is conceptual, so you don't need a running Firestore project to follow along. A little context is enough. You will need:</p>
<ul>
<li><p>Basic JavaScript syntax, since every code example uses the modular Firebase JS SDK (v9+)</p>
</li>
<li><p>Basic familiarity with JSON objects (keys, values, nested objects, arrays)</p>
</li>
<li><p>Some exposure to SQL or relational databases helps, since the guide leans on that comparison throughout (but it's not required)</p>
</li>
<li><p>(Optional) A free Firebase project, if you want to try the examples yourself. The <a href="https://firebase.google.com/docs/firestore/quickstart">Firestore quickstart</a> walks you through setting one up.</p>
</li>
</ul>
<p>No prior NoSQL or Firestore experience is needed.</p>
<h2 id="heading-the-relational-mindset-how-sql-handles-data">The Relational Mindset: How SQL Handles Data</h2>
<p>In a relational database, data is organized into tables linked by explicit relationships. This approach relies on <strong>normalization</strong> to eliminate data redundancy.</p>
<p>For example, to store users and their respective countries, we split the data into two tables:</p>
<ul>
<li><p><code>Users</code>: columns <code>id</code> (PK), <code>last_name</code>, <code>first_name</code>, <code>#country_id</code> (FK a foreign key)</p>
</li>
<li><p><code>Countries</code>: columns <code>country_id</code> (PK), <code>country_name</code></p>
</li>
</ul>
<p>With a row like <code>1, MINTOUMBA, Caleb, 1</code> in <code>Users</code> and <code>1, Canada</code> in <code>Countries</code>, we automatically know that Caleb belongs to Canada through the foreign key <code>#country_id</code>. We never had to write the word "Canada" inside the <code>Users</code> table itself.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66f71ee288cc311f84e563bc/ac10a248-0b5e-4be7-a738-3a6bdd54c1d7.png" alt="Relational model showing a Users table linked to a Countries table through a foreign key" style="display:block;margin:0 auto" width="2179" height="1019" loading="lazy">

<p><strong>The SQL trade-off:</strong> writes are lightweight (you only update data in one place), but reads are heavier, because you have to perform a database join (<code>JOIN</code>) every time you want to display a user's country name.</p>
<p>That's exactly the opposite of how Firestore works, as we'll see next.</p>
<h2 id="heading-the-firestore-paradigm-nosql-with-relationships">The Firestore Paradigm: NoSQL with Relationships</h2>
<p>Firestore is a <strong>NoSQL</strong> document database – literally <em>Not Only SQL</em>. It stores JSON-like documents grouped into collections, with no enforced schema.</p>
<p>For most of Firestore's history, that also meant no native joins and no <code>GROUP BY</code>. The standard query engine simply didn't support them, and any aggregation beyond <code>count()</code>, <code>sum()</code>, and <code>average()</code> had to happen in your application code.</p>
<p>That's still true today for <strong>Standard edition</strong>, which remains the default and the one most mobile/web apps run on and the one this guide focuses on.</p>
<p>Google has since introduced <strong>Firestore Enterprise edition</strong>, built around a new <strong>Pipeline</strong> query engine that reached general availability in April 2026. Pipelines add a multi-stage query syntax and hundreds of new functions, including relational-style joins through correlated subqueries and a real <code>aggregate(...)</code> step with grouping Firestore's equivalent of SQL's <code>GROUP BY</code>.</p>
<p><strong>Does this mean data modeling doesn't matter anymore?</strong> Not for most apps. Pipeline queries run within a 60-second timeout and a 128 MiB working-memory limit, can fall back to full collection scans when no index exists, and critically, Enterprise edition drops real-time listeners and offline support (which most Firestore client apps depend on).</p>
<p>Pipelines are a genuine escape hatch for analytical, admin, or reporting queries. They're not a drop-in replacement for the read-optimized structure your app's everyday screens still need.</p>
<p>If you're building a typical client-facing app on Standard edition, the embedding and denormalization strategies below are still how you model relationships.</p>
<p>But <strong>NoSQL doesn't mean "no relationships"</strong> even on Standard edition. You can and should build robust relationships between your collections. The difference is that Firestore won't enforce or resolve them for you the way a <code>JOIN</code> does by default. It's up to you, the developer, to build and query those relationships explicitly, and to maintain data integrity through your application code or Cloud Functions unless you've specifically opted into Enterprise edition for Pipeline-powered joins.</p>
<h2 id="heading-the-core-building-blocks-documents-and-collections">The Core Building Blocks: Documents and Collections</h2>
<p>Before designing any schema, let's define Firestore's two core building blocks:</p>
<ul>
<li><p><strong>Document</strong>: the basic unit of storage. It's a JSON-like object, identified by a unique ID, containing typed fields (strings, numbers, booleans, timestamps, geopoints, or references to other documents).</p>
</li>
<li><p><strong>Collection</strong>: a container for documents. Unlike SQL tables, documents in the same collection don't need to share the same structure.</p>
</li>
</ul>
<p>What makes Firestore unique is its hierarchical nature: <strong>a document can contain sub-collections</strong>, which contain more documents, which can themselves contain more sub-collections, and so on.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66f71ee288cc311f84e563bc/3d49ff84-8648-4c15-bcc3-60caeb540a77.png" alt="Firestore hierarchy diagram showing a posts collection containing the post_001 document, which holds a comments sub-collection with individual comment documents" style="display:block;margin:0 auto" width="2179" height="1259" loading="lazy">

<p>In the diagram above, the root <code>posts</code> collection contains the document <code>post_001</code>, which itself hosts a <code>comments</code> sub-collection containing the individual comment documents <code>comment_001</code> and <code>comment_002</code>. You can nest collections and documents several levels deep, but as we'll see later, it's best to do so sparingly.</p>
<p><strong>Crucial rule:</strong> sub-collections are never retrieved automatically when you read a parent document. Unlike a SQL <code>JOIN</code>, you must always perform a separate, explicit query to read a sub-collection.</p>
<h2 id="heading-the-golden-rule-model-for-reads-not-writes">The Golden Rule: Model for Reads, Not Writes</h2>
<p>This is the single most important concept in NoSQL modeling, and the one developers coming from SQL forget most often: <strong>structure your data based on how your app queries it, not on how it gets written.</strong></p>
<p>Before writing any database code, ask yourself:</p>
<ul>
<li><p>Which screens in my app will display this data?</p>
</li>
<li><p>Do I need this piece of data on its own, or always alongside another one?</p>
</li>
<li><p>Do I read this information significantly more often than I write or update it?</p>
</li>
</ul>
<p>If your users view a writer's profile 10,000 times for every single time that writer updates their username, optimize for the reads: duplicate the username directly inside each post. That's the exact opposite of the SQL instinct we saw earlier, where you normalize first to avoid redundancy, even if it makes reads heavier.</p>
<h2 id="heading-embedding-vs-referencing-denormalization">Embedding vs. Referencing (Denormalization)</h2>
<p>There are two primary strategies for representing a relationship in Firestore.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66f71ee288cc311f84e563bc/ac6d31d1-9c75-4688-b39a-e01ffa55ea07.png" alt="Side-by-side comparison of embedding comments directly inside a post document versus referencing them through a separate comments sub-collection" style="display:block;margin:0 auto" width="2179" height="1180" loading="lazy">

<h3 id="heading-option-a-embedding-nesting">Option A: Embedding (Nesting)</h3>
<p>You store the related data directly inside the parent document, as an array or a map (object).</p>
<pre><code class="language-js">// A post with its comments embedded
{
  title: "Introduction to Firestore",
  author: "Caleb",
  comments: [
    { user: "Ama", text: "Great post!" },
    { user: "Kofi", text: "Thanks for the examples" }
  ]
}
</code></pre>
<ul>
<li><p><strong>Pros</strong>: a single read retrieves everything, and consistency is guaranteed.</p>
</li>
<li><p><strong>Cons</strong>: Firestore documents have a hard <strong>1 MB size limit</strong>. If the nested list grows indefinitely (comments on a viral post, for instance), your writes will start failing once you hit that limit and every write to the parent document also re-sends the whole document to any client listening in real time.</p>
</li>
<li><p><strong>Best for</strong>: small, bounded lists (tags on an article, a user's settings, a short list of favorites).</p>
</li>
</ul>
<h3 id="heading-option-b-referencing-denormalization">Option B: Referencing (Denormalization)</h3>
<p>You split the entities into separate collections or sub-collections, and deliberately duplicate a few fields to avoid a second read.</p>
<pre><code class="language-js">// posts/post_001
{
  title: "Introduction to Firestore",
  authorId: "uid_123",
  authorName: "Caleb",      // denormalized: avoids a second read to "users"
  authorAvatar: "https://...",
  commentCount: 12          // denormalized counter
}

// posts/post_001/comments/comment_001
{
  userId: "uid_456",
  userName: "Ama",
  text: "Great post!",
  createdAt: Timestamp
}
</code></pre>
<p>Here, we duplicate the author's name and avatar into every post so we don't need an extra read to <code>users</code> every time the post list is displayed.</p>
<p>That's denormalization: we accept controlled redundancy in exchange for faster reads the exact opposite of SQL normalization. The cost is that these copies need updating if the user changes their name (usually handled by a Cloud Function triggered when the <code>users</code> document is updated).</p>
<ul>
<li><p><strong>Pros</strong>: no document size limits, and entities can be queried independently.</p>
</li>
<li><p><strong>Cons</strong>: requires multiple reads if you didn't denormalize enough. If a duplicated value changes, you need code (often a Cloud Function) to propagate the update everywhere it's copied.</p>
</li>
<li><p><strong>Best for</strong>: dynamic, fast-growing data (comments, order history, activity logs).</p>
</li>
</ul>
<p><strong>A more precise rule of thumb</strong>: whether to <em>reference instead of embed</em> depends on volume. Sub-collections handle unbounded growth (comments, order history) better than arrays.</p>
<p>Whether to <em>denormalize a given field</em> depends on the cost of keeping it in sync, not how often it changes: a counter you update in place with an atomic increment (<code>commentCount</code>, <code>likeCount</code>) has no other copy to synchronize, so it's cheap to denormalize regardless of frequency.</p>
<p>A copied value like <code>authorName</code>, on the other hand, is duplicated across every document that references it. It's safe to denormalize only if it changes rarely, since any change means propagating the update everywhere it's been copied.</p>
<h2 id="heading-how-to-model-relationships-1-1-1-n-n-n">How to Model Relationships (1-1, 1-N, N-N)</h2>
<h3 id="heading-one-to-one-1-1">One-to-One (1-1)</h3>
<p>Either embed the fields in the same document, or store them in a separate collection using the exact same document ID, for example <code>users/uid_123</code> and <code>privateProfiles/uid_123</code>. This is perfect for separating public data from sensitive data that needs different security rules.</p>
<h3 id="heading-one-to-many-1-n">One-to-Many (1-N)</h3>
<img src="https://cdn.hashnode.com/uploads/covers/66f71ee288cc311f84e563bc/cc6057e0-f3c9-42dd-827b-4546033b6248.png" alt="One-to-many relationship diagram showing a post document linked to multiple comment documents through a sub-collection" style="display:block;margin:0 auto" width="2179" height="980" loading="lazy">

<p>There are three main options, depending on volume and query direction:</p>
<ol>
<li><p>A <strong>sub-collection</strong> (<code>posts/post_001/comments/*</code>) is ideal when you almost always query comments <em>through</em> their parent post, and volume can be large.</p>
</li>
<li><p>A <strong>root collection with a reference</strong> (<code>comments</code> with a <code>postId</code> field) is useful if you also need to query all comments by a given user, independently of the post (<code>where("userId", "==", uid)</code>).</p>
</li>
<li><p>Use an <strong>embedded array</strong> only if the volume stays small and bounded (see Option A above).</p>
</li>
</ol>
<h3 id="heading-many-to-many-n-n">Many-to-Many (N-N)</h3>
<p>This is the trickiest one in NoSQL, since there's no automatic join table like in SQL. There are three common patterns:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66f71ee288cc311f84e563bc/b8b96fab-5180-43fb-98fd-fe16faac162f.png" alt="Many-to-many relationship diagram showing a memberships junction collection linking users and groups" style="display:block;margin:0 auto" width="2179" height="1060" loading="lazy">

<p><strong>(1). Junction collection</strong> the equivalent of a SQL pivot table:</p>
<pre><code class="language-js">// memberships/{membershipId}
{
  userId: "uid_123",
  groupId: "group_789",
  role: "admin",
  joinedAt: Timestamp
}
</code></pre>
<p>You can then query <code>.where("userId", "==", uid)</code> to find all groups a user belongs to, or <code>.where("groupId", "==", gid)</code> to find all members of a group.</p>
<p><strong>(2). ID arrays on both sides</strong> (cross-denormalization):</p>
<pre><code class="language-js">// users/uid_123      -&gt; groupIds: ["group_789", "group_456"]
// groups/group_789   -&gt; memberIds: ["uid_123", "uid_456"]
</code></pre>
<p>Fast to read from either side, but reserve this for lists that stay small the 1 MB document limit and the cost of atomically updating long arrays both work against you at scale.</p>
<p><strong>(3). Hybrid approach</strong>, which is the most common pattern in practice: an array for a lightweight relationship rarely queried from the other side (a user's favorite posts), and a junction collection for a relationship queried frequently in both directions and prone to frequent changes (team memberships).</p>
<h2 id="heading-best-practices-and-pitfalls-to-avoid">Best Practices and Pitfalls to Avoid</h2>
<ul>
<li><p><strong>Limit nesting depth:</strong> Firestore allows sub-collections to be nested indefinitely, but beyond two or three levels, your queries and security rules become genuinely hard to maintain. Prefer flattening the structure with references when you can.</p>
</li>
<li><p><strong>Avoid auto-incremented document IDs:</strong> Sequential IDs (<code>user_1</code>, <code>user_2</code>, <code>user_3</code>...) can cause <em>hotspotting</em>: writes pile up on a narrow range of the index, which degrades performance at scale. Let Firestore generate random, evenly distributed IDs unless you have a specific reason not to.</p>
</li>
<li><p><strong>Watch out for composite indexes:</strong> Any query combining multiple <code>.where()</code> filters, or a <code>.where()</code> with an <code>.orderBy()</code> on a different field, requires a composite index. Plan for these during design rather than discovering them in production (Firestore's error messages include a direct link to auto-generate the missing index).</p>
</li>
<li><p><strong>Mind the write rate on "hot" documents:</strong> The recommended maximum <em>sustained</em> write rate to a single document is about <strong>1 write per second</strong>. A document updated very frequently by many different users a global like counter, for example becomes a bottleneck well before that. Firestore can absorb short bursts (5, 10, even 50 writes in one second) by queuing them, but sustained traffic above ~1 write/sec will start producing contention errors. The standard fix is a <em>sharded counter</em>: split the count across several sub-documents and sum them at read time.</p>
</li>
<li><p><strong>Use sub-collections deliberately:</strong> They're convenient, but they always require a separate query. If you almost always need the data together, embedding or denormalization will perform better.</p>
</li>
<li><p><strong>Design security rules alongside your data model:</strong> Firestore's security rules (<code>firestore.rules</code>) should be designed at the same time as your schema a poorly thought-out structure usually makes precise rules much harder to write.</p>
</li>
</ul>
<h2 id="heading-case-study-designing-a-scalable-blog-database">Case Study: Designing a Scalable Blog Database</h2>
<p>Let's bring every principle from this guide together with a concrete example: a blog with posts, comments, and likes.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66f71ee288cc311f84e563bc/d774e41d-28d6-4d0a-81cb-a7bd088992d9.png" alt="Complete Firestore schema for a blog application showing the posts, comments sub-collection, and likes collection" style="display:block;margin:0 auto" width="2379" height="1300" loading="lazy">

<pre><code class="language-js">// posts/{postId}
{
  title: "Modeling Firestore",
  slug: "modeling-firestore",
  authorId: "uid_123",
  authorName: "Caleb",         // denormalized: avoids a second read to "users"
  content: "...",
  tags: ["firebase", "nosql"], // embedded: small, bounded list
  commentCount: 3,             // denormalized counter
  likeCount: 47,               // denormalized counter (shard it if traffic is high)
  createdAt: Timestamp
}

// posts/{postId}/comments/{commentId}  → sub-collection: read together with the post
{
  userId: "uid_456",
  userName: "Ama",
  text: "Excellent article",
  createdAt: Timestamp
}

// likes/{likeId}  → root collection + reference
{                    // lets you quickly check if ONE user liked ONE post
  postId: "post_001",
  userId: "uid_456"
}
</code></pre>
<p>Each choice here answers a specific read pattern. Tags are always displayed alongside the post, so they're embedded. Comments can grow large in number and are almost always fetched together with their parent post, so they live in a sub-collection. Likes need to be queried both by post <em>and</em> by user to check whether <em>this</em> user already liked <em>this</em> post so they sit in a root collection with two indexable fields.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In SQL, you normalize to eliminate redundancy, and you pay for that choice at read time, via joins. In Firestore, it's the opposite: you accept controlled redundancy (denormalization) to make reads instant and cheap, at the cost of slightly heavier writes.</p>
<p>Modeling data in Firestore isn't about applying relational habits with a different syntax. It's a genuinely different way of thinking, centered on your app's read patterns.</p>
<p>Always ask "how will I read this data, and how often?" before choosing between embedding, referencing, or a sub-collection. Also, keep Firestore's concrete limits in mind (1 MB per document, composite indexes, hotspotting) from the design phase rather than discovering them in production.</p>
<p>That balance between read simplicity and write cost is what separates a Firestore database that scales gracefully from one you'll be rewriting six months from now.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
