<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged mongodb</title>
<link>https://coderlegion.com/tag/mongodb</link>
<description>Powered by Question2Answer</description>
<item>
<title>Your Pagination Is Showing Users the Same Item Twice (and Skipping Others). Here's Why Offset Breaks</title>
<link>https://coderlegion.com/32501/your-pagination-showing-users-the-same-item-twice-and-skipping-others-heres-offset-breaks</link>
<description>&lt;p&gt;A user scrolls through your feed. Page 1 ends with &quot;Post 20&quot;. They tap &quot;Next&quot;, and page 2 starts with... &quot;Post 20&quot; again.&lt;/p&gt;
&lt;p&gt;Another user pages through an admin table and never sees order #4817. It exists. It is in the database. It just never showed up on any page.&lt;/p&gt;
&lt;p&gt;Nobody changed any code. The data changed underneath the pagination, and your &lt;code&gt;skip&lt;/code&gt; and &lt;code&gt;limit&lt;/code&gt; could not cope.&lt;/p&gt;
&lt;h3 id=&quot;the-code-everyone-writes&quot;&gt;The Code Everyone Writes&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;app.get('/api/posts', async (req, res) =&amp;gt; {
  const page = Number(req.query.page) || 1;
  const limit = 20;

  const posts = await Post.find()
    .sort({ createdAt: -1 })
    .skip((page - 1) * limit)
    .limit(limit);

  res.json(posts);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is simple, it works in development, and &lt;code&gt;?page=2&lt;/code&gt; is easy to understand. That is why it is everywhere.&lt;/p&gt;
&lt;h3 id=&quot;why-it-breaks&quot;&gt;Why It Breaks&lt;/h3&gt;
&lt;p&gt;Offset pagination says &quot;skip the first N rows, then give me the next 20&quot;. It counts positions, and positions move when the data does.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Duplicates.&lt;/strong&gt; Someone publishes a new post while a reader is on page 1. Every existing post shifts down one position. When the reader requests page 2, the last item of page 1 is now the first item of page 2.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skips.&lt;/strong&gt; The reverse: a post is deleted while the reader is on page 1. Everything shifts up one, and the first item of what would have been page 2 slides into page 1's range. The reader never sees it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Before:  [1 2 3 4 5 | 6 7 8 9 10]    page 1 = 1-5, page 2 = 6-10
New item arrives at the top:
After:   [0 1 2 3 4 | 5 6 7 8 9]     page 2 now starts with 5, which they already saw
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-second-problem-it-gets-slower-on-deep-pages&quot;&gt;The Second Problem: It Gets Slower on Deep Pages&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;skip(100000)&lt;/code&gt; does not jump to row 100,000. The database has to walk past 100,000 documents and throw them away before returning your 20. Page 1 is fast. Page 5,000 is slow, and the cost grows with the page number.&lt;/p&gt;
&lt;p&gt;This is why a feed that feels fine with 1,000 rows slows down quietly at a million.&lt;/p&gt;
&lt;h3 id=&quot;the-fix-cursor-pagination&quot;&gt;The Fix: Cursor Pagination&lt;/h3&gt;
&lt;p&gt;Instead of &quot;skip N&quot;, say &quot;give me the 20 items &lt;strong&gt;after this one&lt;/strong&gt;&quot;. The anchor is a &lt;strong&gt;cursor&lt;/strong&gt;: a value that identifies where the last page ended.&lt;/p&gt;
&lt;p&gt;A new item arriving at the top cannot shift your place, because you are not counting positions. You are standing on a specific item.&lt;/p&gt;
&lt;h4 id=&quot;step-1-sort-by-something-stable-with-a-tie-breaker&quot;&gt;Step 1: Sort by something stable, with a tie-breaker&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;createdAt&lt;/code&gt; alone is not enough, because two documents can share the same timestamp. Add &lt;code&gt;_id&lt;/code&gt; as a tie-breaker so the order is always exact:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;.sort({ createdAt: -1, _id: -1 })
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-2-support-the-query-with-an-index&quot;&gt;Step 2: Support the query with an index&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;PostSchema.index({ createdAt: -1, _id: -1 });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this index, the database jumps straight to the cursor and reads 20 documents, whether you are on page 1 or page 5,000.&lt;/p&gt;
&lt;h4 id=&quot;step-3-query-after-the-cursor-&quot;&gt;Step 3: Query &quot;after the cursor&quot;&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;app.get('/api/posts', async (req, res) =&amp;gt; {
  const limit = 20;
  const cursor = req.query.cursor ? decodeCursor(req.query.cursor) : null;

  const filter = cursor
    ? {
        $or: [
          { createdAt: { $lt: cursor.createdAt } },
          { createdAt: cursor.createdAt, _id: { $lt: cursor._id } },
        ],
      }
    : {};

  const rows = await Post.find(filter)
    .sort({ createdAt: -1, _id: -1 })
    .limit(limit + 1); // fetch one extra to know if there is a next page

  const hasMore = rows.length &amp;gt; limit;
  const items = hasMore ? rows.slice(0, limit) : rows;
  const last = items[items.length - 1];

  res.json({
    items,
    nextCursor: hasMore ? encodeCursor(last) : null,
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;$or&lt;/code&gt; says: &quot;older than the cursor, or the same timestamp but a smaller &lt;code&gt;_id&lt;/code&gt;&quot;. That second branch is what stops items with identical timestamps from being skipped or repeated.&lt;/p&gt;
&lt;h4 id=&quot;step-4-encode-the-cursor-so-clients-do-not-depend-on-it&quot;&gt;Step 4: Encode the cursor so clients do not depend on it&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const encodeCursor = (doc) =&amp;gt;
  Buffer.from(JSON.stringify({ createdAt: doc.createdAt, _id: doc._id })).toString('base64url');

const decodeCursor = (str) =&amp;gt; {
  const { createdAt, _id } = JSON.parse(Buffer.from(str, 'base64url').toString());
  return { createdAt: new Date(createdAt), _id: new mongoose.Types.ObjectId(_id) };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clients treat it as an opaque string and send it back as it is. You can change what is inside later without breaking anyone. In production, validate the decoded values and handle a malformed cursor with a &lt;code&gt;400&lt;/code&gt;, not a crash.&lt;/p&gt;
&lt;h3 id=&quot;what-you-give-up&quot;&gt;What You Give Up&lt;/h3&gt;
&lt;p&gt;Cursor pagination has a trade-off, and you should know it before switching:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No &quot;jump to page 47&quot;.&lt;/strong&gt; You can only go forward (or back, with a second cursor). For feeds and infinite scroll, that is perfect. For a numbered page bar, it is not.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No cheap total count.&lt;/strong&gt; &lt;code&gt;countDocuments&lt;/code&gt; on a large collection is not free either way, so many apps drop the &quot;page 3 of 500&quot; display.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;when-offset-is-still-fine&quot;&gt;When Offset Is Still Fine&lt;/h3&gt;
&lt;p&gt;Do not rewrite everything. Offset pagination is a reasonable choice for small, slow-changing tables, like an admin list with a few hundred rows, where users really do want page numbers.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Situation &lt;/th&gt;&lt;th&gt;Use&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Feed, timeline, infinite scroll &lt;/td&gt;&lt;td&gt;Cursor&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Large collection, deep pages &lt;/td&gt;&lt;td&gt;Cursor&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Data changes while people browse &lt;/td&gt;&lt;td&gt;Cursor&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Small admin table with page numbers &lt;/td&gt;&lt;td&gt;Offset is fine&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;the-rule&quot;&gt;The Rule&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Offset pagination counts positions, and positions change. Cursor pagination anchors to a specific item, so it stays correct when data is added or removed.&lt;/strong&gt; Always sort by a unique, indexed combination, and fetch one extra row to know whether there is a next page.&lt;/p&gt;
&lt;p&gt;Quick audit: search your project for &lt;code&gt;.skip(&lt;/code&gt;. For each hit, ask whether the collection can grow past a few thousand documents, or change while someone is paging through it.&lt;/p&gt;
&lt;p&gt;If you work with MongoDB, I collect more patterns like this, around indexes and queries, in my &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.com/blog/mongodb-mongoose-patterns-2026&quot;&gt;MongoDB and Mongoose patterns guide&lt;/a&gt;, and you can browse my Next.js &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.com&quot;&gt;templates on pixelanas.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Have you ever been bitten by duplicate or missing items in a paginated list? Tell me how you noticed in the comments.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32501/your-pagination-showing-users-the-same-item-twice-and-skipping-others-heres-offset-breaks</guid>
<pubDate>Mon, 26 Oct 2026 11:11:24 +0000</pubDate>
</item>
<item>
<title>Your 'Pay Now' Button Can Charge Twice. Idempotency Keys Are the Fix Nobody Adds Until It Happens</title>
<link>https://coderlegion.com/32382/your-pay-now-button-can-charge-twice-idempotency-keys-are-the-fix-nobody-adds-until-happens</link>
<description>&lt;p&gt;A customer clicks &quot;Pay now&quot;. Nothing happens for three seconds, so they click again. Or their phone loses signal for a moment and the app quietly retries the request. Or your load balancer times out and sends the request again.&lt;/p&gt;
&lt;p&gt;Your server received the same order twice, and it did exactly what you told it to do: it created two orders.&lt;/p&gt;
&lt;p&gt;This is not a rare edge case. It is the default behavior of every &lt;code&gt;POST&lt;/code&gt; endpoint you have ever written.&lt;/p&gt;
&lt;h3 id=&quot;the-endpoint-that-looks-fine&quot;&gt;The Endpoint That Looks Fine&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;app.post('/api/orders', requireAuth, async (req, res) =&amp;gt; {
  const order = await createOrder(req.user.id, req.body);
  await chargeCard(order);
  res.status(201).json({ orderId: order.id });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing is wrong with this code in isolation. The problem is what it cannot know: whether &lt;em&gt;this&lt;/em&gt; request is new, or a repeat of one that already succeeded.&lt;/p&gt;
&lt;h3 id=&quot;why-this-is-hard-to-catch&quot;&gt;Why This Is Hard to Catch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;You test with one request at a time.&lt;/strong&gt; The bug needs two requests with the same intent, close together.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timeouts are ambiguous.&lt;/strong&gt; When a client times out, it does not know if the server never got the request, or did the work and the response got lost. Retrying is the only safe-looking choice, and it is the one that duplicates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Disabling the button is not enough.&lt;/strong&gt; It helps with double clicks but not with network retries, mobile apps, or another service calling your API.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-idea-same-key-same-result&quot;&gt;The Idea: Same Key, Same Result&lt;/h3&gt;
&lt;p&gt;An &lt;strong&gt;idempotent&lt;/strong&gt; operation is one where doing it twice has the same effect as doing it once. &lt;code&gt;POST&lt;/code&gt; is not idempotent by default, but you can make it behave that way:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The client generates a unique &lt;strong&gt;idempotency key&lt;/strong&gt; for each &lt;em&gt;intent&lt;/em&gt; and sends it in a header.&lt;/li&gt;
&lt;li&gt;The server remembers the key together with the result.&lt;/li&gt;
&lt;li&gt;If the same key arrives again, the server returns the saved result and does not do the work again.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Payment providers such as Stripe use exactly this pattern with an &lt;code&gt;Idempotency-Key&lt;/code&gt; header.&lt;/p&gt;
&lt;h3 id=&quot;a-working-version-with-express-and-mongodb&quot;&gt;A Working Version With Express and MongoDB&lt;/h3&gt;
&lt;p&gt;First, a unique index, plus an expiry so old keys clean themselves up:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;await keys.createIndex({ userId: 1, key: 1 }, { unique: true });
await keys.createIndex({ createdAt: 1 }, { expireAfterSeconds: 86400 });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;app.post('/api/orders', requireAuth, async (req, res) =&amp;gt; {
  const key = req.get('Idempotency-Key');
  if (!key) return res.status(400).json({ error: 'Idempotency-Key header required' });

  const userId = req.user.id;
  const bodyHash = hash(stableStringify(req.body));

  // 1. Try to claim the key. Only one request can win this insert.
  try {
    await keys.insertOne({ userId, key, bodyHash, status: 'processing', createdAt: new Date() });
  } catch (err) {
    if (err.code !== 11000) throw err; // 11000 = duplicate key

    const saved = await keys.findOne({ userId, key });
    if (saved.bodyHash !== bodyHash) {
      return res.status(422).json({ error: 'Key was used with a different request' });
    }
    if (saved.status === 'processing') {
      return res.status(409).json({ error: 'Request is still being processed' });
    }
    return res.status(saved.statusCode).json(saved.response); // replay
  }

  // 2. We own the key. Do the work once.
  try {
    const order = await createOrder(userId, req.body);
    await chargeCard(order);

    const response = { orderId: order.id };
    await keys.updateOne(
      { userId, key },
      { $set: { status: 'done', statusCode: 201, response } }
    );
    return res.status(201).json(response);
  } catch (err) {
    await keys.deleteOne({ userId, key }); // let the client retry
    throw err;
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-race-condition-most-examples-miss&quot;&gt;The Race Condition Most Examples Miss&lt;/h3&gt;
&lt;p&gt;A common first attempt is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const existing = await keys.findOne({ userId, key });
if (existing) return res.json(existing.response);
// ... do the work ...
await keys.insertOne({ userId, key, response });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two simultaneous requests both run &lt;code&gt;findOne&lt;/code&gt;, both see nothing, and both do the work. You added a check and still got a duplicate.&lt;/p&gt;
&lt;p&gt;The fix is in the version above: &lt;strong&gt;claim the key first with an insert that can only succeed once&lt;/strong&gt;, and let the unique index decide the winner. The database settles the race, not your code.&lt;/p&gt;
&lt;h3 id=&quot;four-details-that-matter&quot;&gt;Four Details That Matter&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Scope keys per user.&lt;/strong&gt; Two users who happen to generate the same key must not see each other's responses. That is why the index is on &lt;code&gt;{ userId, key }&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Compare the request body.&lt;/strong&gt; If the same key arrives with a different body, that is a bug or an attack, not a retry. Return an error instead of replaying a response that does not match the request. Use a stable stringify (sorted keys) for the hash, or two identical bodies with different property order will hash differently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Generate the key per intent, not per attempt.&lt;/strong&gt; On the client, create the key when the user starts the action and &lt;strong&gt;reuse it for every retry&lt;/strong&gt; of that action. A new key per retry brings the duplicate right back.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const key = crypto.randomUUID(); // once, when the checkout starts
await fetch('/api/orders', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'Idempotency-Key': key },
  body: JSON.stringify(cart),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. Pass the key on to your payment provider too.&lt;/strong&gt; If your server crashes after charging the card but before saving the result, your own table cannot help. A key sent to the provider protects that gap, and the expiry index clears any stuck &lt;code&gt;processing&lt;/code&gt; record.&lt;/p&gt;
&lt;h3 id=&quot;the-rule-1&quot;&gt;The Rule&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Any request that creates something or moves money must be safe to send twice.&lt;/strong&gt; Do not rely on disabled buttons or &quot;the network is reliable&quot;. Give the client a key, claim it atomically on the server, and replay the saved result on a repeat.&lt;/p&gt;
&lt;p&gt;Quick audit: list your &lt;code&gt;POST&lt;/code&gt; endpoints that create orders, payments, invoices, emails or bookings. For each, ask what happens if the request arrives twice, a second apart. If the answer is &quot;two of them&quot;, you have found your next ticket.&lt;/p&gt;
&lt;p&gt;If you work with MongoDB, my &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.com/blog/mongodb-mongoose-patterns-2026&quot;&gt;MongoDB and Mongoose patterns guide&lt;/a&gt; has more on using the database to enforce rules, and you can browse my Next.js &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.com&quot;&gt;templates on pixelanas.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Have you ever shipped a double-charge or a duplicate-order bug? Tell me how you found out, in the comments.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32382/your-pay-now-button-can-charge-twice-idempotency-keys-are-the-fix-nobody-adds-until-happens</guid>
<pubDate>Sun, 25 Oct 2026 11:09:21 +0000</pubDate>
</item>
<item>
<title>Demystifying MongoDB Write Concern and Read Concern in Node.js</title>
<link>https://coderlegion.com/31698/demystifying-mongodb-write-concern-and-read-concern-in-node-js</link>
<description>&lt;h3 id=&quot;what-are-write-concern-and-read-concern-&quot;&gt;What Are Write Concern and Read Concern?&lt;/h3&gt;
&lt;p&gt;In MongoDB, &lt;strong&gt;write concern&lt;/strong&gt; and &lt;strong&gt;read concern&lt;/strong&gt; are configuration settings that specify the level of guarantee you require when saving or retrieving data across a distributed cluster. A write concern determines how many database nodes must successfully record an update—and whether that data must be written to disk—before MongoDB reports success to your application. A read concern determines how fresh and consistent the data returned by a query must be, protecting your application from reading temporary data that might later be reversed.&lt;/p&gt;
&lt;p&gt;Together, these two settings let developers choose where their application sits on the classic spectrum between maximum execution speed and rock-solid data safety.&lt;/p&gt;
&lt;h3 id=&quot;the-real-world-analogy-certified-mail-vs-kitchen-whiteboards&quot;&gt;The Real-World Analogy: Certified Mail vs. Kitchen Whiteboards&lt;/h3&gt;
&lt;p&gt;Imagine you run a bustling catering kitchen with multiple head chefs.&lt;/p&gt;
&lt;p&gt;When taking an order, you have two choices for your &lt;strong&gt;write concern&lt;/strong&gt;. You could scribble the customer's allergy request on a loose scrap of paper and immediately yell &quot;Order placed!&quot; (a fast write, but if you drop the paper, the order is lost). Alternatively, you could walk the order ticket over to the main order board, watch two sous-chefs sign off on it, and wait for them to file it in the metal lockbox before confirming the order with the customer. The second route takes several seconds longer, but you are guaranteed that the kitchen will never forget the order even if one chef walks out.&lt;/p&gt;
&lt;p&gt;Now consider the &lt;strong&gt;read concern&lt;/strong&gt;. If a waiter needs to know the soup of the day, they can either glance at a whiteboard where someone started drafting ideas with an erasable marker, or they can refuse to serve anything until they see the official menu stamped and approved by the head kitchen manager. Glancing at the draft whiteboard is instantaneous, but you might serve an experimental soup that the manager ultimately rejects five minutes later.&lt;/p&gt;
&lt;h3 id=&quot;why-it-matters-in-daily-software-engineering&quot;&gt;Why It Matters in Daily Software Engineering&lt;/h3&gt;
&lt;p&gt;Modern web architectures rarely run on a single database server. Instead, they use replica sets—groups of connected servers where one &quot;primary&quot; node handles writes and multiple &quot;secondary&quot; nodes replicate that data for redundancy. &lt;/p&gt;
&lt;p&gt;If you leave MongoDB on default settings during high-traffic moments, subtle race conditions emerge:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Rollback Catastrophes:&lt;/strong&gt; If an API endpoint confirms an e-commerce order after writing only to the primary node, and that primary node suddenly experiences a hardware failure before syncing to secondary nodes, the cluster elects a new primary. Your customer's order is wiped out, yet your payment processor already charged their credit card.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dirty Reads:&lt;/strong&gt; A background worker updates a bank balance. A microservice queries the secondary replica before the primary finishes replicating. If that transaction aborts or fails over, the microservice has operated on data that never officially existed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By tuning write and read concerns, software engineers intentionally shield critical operations (payments, account transfers, credential updates) from data loss while letting low-risk operations (logging, view counters, user analytics) run with blazing speed.&lt;/p&gt;
&lt;h3 id=&quot;implementation-in-node-js&quot;&gt;Implementation in Node.js&lt;/h3&gt;
&lt;p&gt;Here is how you apply write and read concerns when working with the official MongoDB Node.js driver:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { MongoClient } from 'mongodb';

const client = new MongoClient('mongodb://localhost:27017,localhost:27018/?replicaSet=rs0');

async function processPayment(orderId, paymentDetails) {
  await client.connect();
  const db = client.db('store');

  // Configure high-durability options for financial transactions
  const ordersCollection = db.collection('orders', {
    writeConcern: {
      w: 'majority',      // Must be acknowledged by more than half of the replica set nodes
      j: true,            // Must be written to the on-disk journal before confirming
      wtimeout: 5000      // Abort if replicas do not respond within 5 seconds
    },
    readConcern: {
      level: 'majority'   // Only read data committed by a majority of nodes
    }
  });

  // Step 1: Safely write with acknowledgment
  const writeResult = await ordersCollection.updateOne(
    { _id: orderId },
    { $set: { status: 'PAID', payment: paymentDetails } }
  );

  // Step 2: Read guaranteed, durable data
  const confirmedOrder = await ordersCollection.findOne({ _id: orderId });

  console.log('Payment processed safely:', confirmedOrder._id);
  return confirmedOrder;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, specifying &lt;code&gt;w: 'majority'&lt;/code&gt; and &lt;code&gt;j: true&lt;/code&gt; ensures that the write cannot be rolled back if the primary server abruptly crashes. Setting &lt;code&gt;level: 'majority'&lt;/code&gt; on the read ensures your application reads only finalized records.&lt;/p&gt;
&lt;h3 id=&quot;key-takeaway&quot;&gt;Key Takeaway&lt;/h3&gt;
&lt;p&gt;Database speed and data reliability exist on a continuous slider: write concern dictates how hard your cluster works to preserve your updates, while read concern dictates how strictly your cluster verifies the stability of what you fetch. Mastering both allows you to protect financial transactions and vital data without unnecessarily throttling high-volume, low-risk endpoints across your stack.&lt;/p&gt;
&lt;hr&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;a href=&quot;https://www.buymeacoffee.com/saurav_tb_pandey&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;
    &lt;img src=&quot;https://img.shields.io/badge/Buy_Me_A_Coffee-FFDD00?style=for-the-badge&amp;amp;logo=buy-me-a-coffee&amp;amp;logoColor=black&quot; alt=&quot;Buy Me A Coffee&quot;&gt;
&lt;p&gt;  &lt;/p&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Enjoyed this breakdown? Support my open-source work on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.buymeacoffee.com/saurav_tb_pandey&quot;&gt;Buy Me a Coffee&lt;/a&gt; or explore &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/react-hook-lab&quot;&gt;react-hook-lab&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/10/how-mongodb-write-concern-and-read.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31698/demystifying-mongodb-write-concern-and-read-concern-in-node-js</guid>
<pubDate>Mon, 19 Oct 2026 09:22:10 +0000</pubDate>
</item>
<item>
<title>Your Mongoose pre('save') Hook Is Being Silently Skipped by Half Your Update Calls</title>
<link>https://coderlegion.com/30913/your-mongoose-pre-save-hook-is-being-silently-skipped-by-half-your-update-calls</link>
<description>&lt;p&gt;This is one of the more consequential Mongoose gotchas because of exactly what tends to live inside a &lt;code&gt;pre('save')&lt;/code&gt; hook, password hashing, slug generation, validation logic, and exactly which methods silently bypass it without ever telling you.&lt;/p&gt;
&lt;h3 id=&quot;the-setup&quot;&gt;The Setup&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// models/User.ts
const userSchema = new Schema({
  email: String,
  password: String,
});

userSchema.pre('save', async function (next) {
  if (this.isModified('password')) {
    this.password = await bcrypt.hash(this.password, 12);
  }
  next();
});

export const User = model('User', userSchema);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a new user with &lt;code&gt;User.create()&lt;/code&gt; or &lt;code&gt;new User(data).save()&lt;/code&gt;, and this works exactly as intended. The password arrives hashed, every time, without the rest of your codebase needing to remember to call &lt;code&gt;bcrypt.hash&lt;/code&gt; manually anywhere. It feels like a solved problem.&lt;/p&gt;
&lt;h3 id=&quot;where-it-actually-breaks&quot;&gt;Where It Actually Breaks&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// A password reset flow
export async function resetPassword(userId: string, newPassword: string) {
  await User.findByIdAndUpdate(userId, { password: newPassword });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This runs without error. It updates the user's password field in the database. It also never calls &lt;code&gt;pre('save')&lt;/code&gt;, which means the password gets stored in &lt;strong&gt;plain text&lt;/strong&gt;, directly in your database, with no error, no warning, and a function name, &lt;code&gt;findByIdAndUpdate&lt;/code&gt;, that reads as a completely reasonable, idiomatic way to update a user record.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;findByIdAndUpdate&lt;/code&gt;, &lt;code&gt;findOneAndUpdate&lt;/code&gt;, &lt;code&gt;updateOne&lt;/code&gt;, and &lt;code&gt;updateMany&lt;/code&gt; all operate directly against MongoDB's update commands. They don't instantiate a full Mongoose document and call &lt;code&gt;.save()&lt;/code&gt; on it internally, they issue an update operation straight to the database. Mongoose's document middleware, including &lt;code&gt;pre('save')&lt;/code&gt;, only runs on document instances that actually go through &lt;code&gt;.save()&lt;/code&gt;. These query-based update methods structurally never touch that code path at all.&lt;/p&gt;
&lt;h3 id=&quot;why-this-specifically-gets-missed&quot;&gt;Why This Specifically Gets Missed&lt;/h3&gt;
&lt;p&gt;Both approaches, &lt;code&gt;.save()&lt;/code&gt; and &lt;code&gt;findByIdAndUpdate&lt;/code&gt;, look like equally valid, equally idiomatic ways to update a Mongoose document, and most Mongoose tutorials use both interchangeably without flagging that they behave completely differently with respect to middleware. A codebase that uses &lt;code&gt;.save()&lt;/code&gt; for creating users and &lt;code&gt;findByIdAndUpdate&lt;/code&gt; for updating them, which is an extremely common and reasonable-looking pattern, ends up with password hashing that works perfectly at signup and silently doesn't exist at all for a password reset, profile update, or admin-initiated password change, unless someone happens to specifically test that exact path with a tool that shows raw database contents.&lt;/p&gt;
&lt;h3 id=&quot;the-fix-use-query-middleware-not-just-document-middleware&quot;&gt;The Fix: Use Query Middleware, Not Just Document Middleware&lt;/h3&gt;
&lt;p&gt;Mongoose provides a separate middleware hook specifically for query-based update methods:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;userSchema.pre('findOneAndUpdate', async function (next) {
  const update = this.getUpdate() as any;

  if (update.password) {
    update.password = await bcrypt.hash(update.password, 12);
  }

  next();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This specifically targets &lt;code&gt;findOneAndUpdate&lt;/code&gt; and &lt;code&gt;findByIdAndUpdate&lt;/code&gt; (which is implemented as a wrapper around &lt;code&gt;findOneAndUpdate&lt;/code&gt; internally). &lt;code&gt;updateOne&lt;/code&gt; and &lt;code&gt;updateMany&lt;/code&gt; need their own separate middleware registration, since Mongoose treats each query method as its own distinct middleware hook rather than one unified &quot;any kind of update&quot; event:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;userSchema.pre('updateOne', async function (next) {
  const update = this.getUpdate() as any;
  if (update.password) {
    update.password = await bcrypt.hash(update.password, 12);
  }
  next();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;a-more-defensive-alternative-standardize-on-one-update-path&quot;&gt;A More Defensive Alternative: Standardize on One Update Path&lt;/h3&gt;
&lt;p&gt;Registering middleware separately for every query method Mongoose offers is viable but easy to let drift out of sync as new update call sites get added later by someone who doesn't know this distinction exists. A more durable fix for anything genuinely sensitive, like password handling, is routing all updates through a single service function that always uses &lt;code&gt;.save()&lt;/code&gt; internally, rather than letting &lt;code&gt;findByIdAndUpdate&lt;/code&gt; calls for that model spread throughout the codebase:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// services/userService.ts
export async function updateUser(userId: string, data: Partial&amp;lt;UserInput&amp;gt;) {
  const user = await User.findById(userId);
  if (!user) throw new Error('User not found');

  Object.assign(user, data);
  await user.save(); // always goes through pre('save') middleware
  return user;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This trades a small amount of performance, an extra read before the write, for a guarantee that every update for this model goes through the exact same validation and transformation logic, with no separate middleware registration required for each query method and no risk of a future &lt;code&gt;findByIdAndUpdate&lt;/code&gt; call quietly skipping it.&lt;/p&gt;
&lt;h3 id=&quot;the-rule-2&quot;&gt;The Rule&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Anything inside a &lt;code&gt;pre('save')&lt;/code&gt; hook only runs for updates that go through &lt;code&gt;.save()&lt;/code&gt;, never for &lt;code&gt;findOneAndUpdate&lt;/code&gt;, &lt;code&gt;findByIdAndUpdate&lt;/code&gt;, &lt;code&gt;updateOne&lt;/code&gt;, or &lt;code&gt;updateMany&lt;/code&gt;.&lt;/strong&gt; For any logic where skipping it has real consequences, password hashing being the clearest example, either register the equivalent query middleware for every update method your codebase actually uses, or standardize on a single service function that always goes through &lt;code&gt;.save()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I route every sensitive field update through a dedicated service layer exactly like this in the auth code behind my &lt;a rel=&quot;nofollow&quot; href=&quot;/blog/mongodb-mongoose-patterns-2026&quot;&gt;Next.js and MongoDB templates&lt;/a&gt;, specifically because this gap is easy to introduce the moment a second developer, or a future version of yourself, reaches for the more convenient-looking &lt;code&gt;findByIdAndUpdate&lt;/code&gt; call.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Go check your own password reset, profile update, and admin edit flows for a &lt;code&gt;findByIdAndUpdate&lt;/code&gt; or &lt;code&gt;updateOne&lt;/code&gt; call on a model with password hashing in &lt;code&gt;pre('save')&lt;/code&gt;. If you find one, check what's actually sitting in that field in your database right now. Drop what you find in the comments.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30913/your-mongoose-pre-save-hook-is-being-silently-skipped-by-half-your-update-calls</guid>
<pubDate>Tue, 13 Oct 2026 09:40:18 +0000</pubDate>
</item>
<item>
<title>🧠 **Coder Legion Dev Log — Building the MyZubster Metaverse from the infrastructure up**</title>
<link>https://coderlegion.com/30192/coder-legion-dev-log-building-the-myzubster-metaverse-from-the-infrastructure-up</link>
<description>&lt;p&gt;🧠 &lt;strong&gt;Coder Legion Dev Log — Building the MyZubster Metaverse from the infrastructure up&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’ve been working on the MyZubster Metaverse with a very specific engineering approach:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;do not start from the 3D layer. Start from identity, authorization, state, lifecycle and failure handling.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The current stack already includes several production-oriented building blocks.&lt;/p&gt;
&lt;h4 id=&quot;identity-and-session-model&quot;&gt;Identity and session model&lt;/h4&gt;
&lt;p&gt;The Metaverse supports both:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;guest characters&lt;/li&gt;
&lt;li&gt;authenticated, account-linked identities&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For authenticated users, canonical character data is resolved server-side so the client cannot freely override trusted identity state.&lt;/p&gt;
&lt;p&gt;This matters because virtual-world logic becomes much harder to secure once identity, permissions and session state are mixed together.&lt;/p&gt;
&lt;h4 id=&quot;realtime-authorization&quot;&gt;Realtime authorization&lt;/h4&gt;
&lt;p&gt;One of the more important backend changes was introducing a &lt;strong&gt;fail-closed authorization path&lt;/strong&gt; for community realtime channels.&lt;/p&gt;
&lt;p&gt;If the membership authority is unavailable, access is denied instead of waiting on buffered database operations or leaving socket authorization in an ambiguous state.&lt;/p&gt;
&lt;p&gt;I added coverage for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;disconnected MongoDB state&lt;/li&gt;
&lt;li&gt;active persisted membership&lt;/li&gt;
&lt;li&gt;membership query failures&lt;/li&gt;
&lt;li&gt;unauthorized access attempts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a small change in code, but a meaningful system-design decision.&lt;/p&gt;
&lt;h4 id=&quot;room-lifecycle&quot;&gt;Room lifecycle&lt;/h4&gt;
&lt;p&gt;The room layer already supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;public / authenticated / private access policies&lt;/li&gt;
&lt;li&gt;invitations&lt;/li&gt;
&lt;li&gt;session creation&lt;/li&gt;
&lt;li&gt;participant join/leave&lt;/li&gt;
&lt;li&gt;host controls&lt;/li&gt;
&lt;li&gt;stage requests&lt;/li&gt;
&lt;li&gt;speaker approval/revocation&lt;/li&gt;
&lt;li&gt;participant moderation&lt;/li&gt;
&lt;li&gt;message reporting&lt;/li&gt;
&lt;li&gt;bounded moderation history&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The important point is that the room is treated as a stateful system, not just a visual container.&lt;/p&gt;
&lt;h4 id=&quot;frontend-state-hardening&quot;&gt;Frontend state hardening&lt;/h4&gt;
&lt;p&gt;I’ve also been cleaning up React lifecycle behavior.&lt;/p&gt;
&lt;p&gt;Recent fixes include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reloading room state when authentication changes&lt;/li&gt;
&lt;li&gt;stabilizing async data loading with &lt;code&gt;useCallback&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;tightening effect dependencies&lt;/li&gt;
&lt;li&gt;keeping UI state aligned with server-side session state&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example recent commits:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0b5e7fa&lt;/code&gt; — refresh Metaverse room when auth state changes&lt;br&gt;
&lt;code&gt;fa94a88&lt;/code&gt; — stabilize frontend loading callback&lt;br&gt;
&lt;code&gt;0347827&lt;/code&gt; — make paid analytics explicit instead of automatic&lt;/p&gt;
&lt;h4 id=&quot;auth-and-observability-testing&quot;&gt;Auth and observability testing&lt;/h4&gt;
&lt;p&gt;I also aligned tests around:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;authenticated Metaverse flows&lt;/li&gt;
&lt;li&gt;server-backed sessions&lt;/li&gt;
&lt;li&gt;auth expiry&lt;/li&gt;
&lt;li&gt;same-origin authenticated requests&lt;/li&gt;
&lt;li&gt;health endpoint behavior&lt;/li&gt;
&lt;li&gt;storage failure semantics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A previous checkpoint also hardened realtime membership authorization and added test coverage around failure states:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0af298e&lt;/code&gt; — fail-closed realtime auth&lt;br&gt;
&lt;code&gt;9ac9f53&lt;/code&gt; — auth session test alignment&lt;br&gt;
&lt;code&gt;5bf365d&lt;/code&gt; — auth + observability test alignment&lt;/p&gt;
&lt;h4 id=&quot;deployment-discipline&quot;&gt;Deployment discipline&lt;/h4&gt;
&lt;p&gt;Another part of the work was CI/CD hygiene.&lt;/p&gt;
&lt;p&gt;I removed automatic activation of paid Vercel Web Analytics from the deployment workflow.&lt;/p&gt;
&lt;p&gt;That means deployment stays deterministic and billing-related features remain explicit operational decisions.&lt;/p&gt;
&lt;h4 id=&quot;current-architecture-direction&quot;&gt;Current architecture direction&lt;/h4&gt;
&lt;p&gt;The development sequence I’m following is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;identity&lt;br&gt;
→ authorization&lt;br&gt;
→ state&lt;br&gt;
→ room lifecycle&lt;br&gt;
→ moderation&lt;br&gt;
→ observability&lt;br&gt;
→ realtime rendering&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is intentional.&lt;/p&gt;
&lt;p&gt;A visually impressive world with weak session handling, unclear authorization or fragile failure modes is still a fragile system.&lt;/p&gt;
&lt;p&gt;So the current priority is to make the platform behavior coherent before connecting the final immersive realtime rendering layer.&lt;/p&gt;
&lt;h4 id=&quot;skills-this-work-is-exercising&quot;&gt;Skills this work is exercising&lt;/h4&gt;
&lt;p&gt;This project has been a practical exercise in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React hooks and state synchronization&lt;/li&gt;
&lt;li&gt;Node.js backend architecture&lt;/li&gt;
&lt;li&gt;REST API design&lt;/li&gt;
&lt;li&gt;realtime authorization&lt;/li&gt;
&lt;li&gt;authentication and session handling&lt;/li&gt;
&lt;li&gt;MongoDB / Mongoose&lt;/li&gt;
&lt;li&gt;fail-closed security patterns&lt;/li&gt;
&lt;li&gt;moderation systems&lt;/li&gt;
&lt;li&gt;automated testing&lt;/li&gt;
&lt;li&gt;observability&lt;/li&gt;
&lt;li&gt;CI/CD&lt;/li&gt;
&lt;li&gt;Vercel deployment workflows&lt;/li&gt;
&lt;li&gt;production debugging&lt;/li&gt;
&lt;li&gt;incremental system hardening&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The immersive layer is still the next milestone.&lt;/p&gt;
&lt;p&gt;But the system underneath it is becoming much more real.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build the world after the rules of the world are reliable.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;coderlegion-softwareengineering-react-nodejs-mongodb-realtime-systemdesign-devops-webdevelopment-metaverse-opensource&quot;&gt;CoderLegion #SoftwareEngineering #React #NodeJS #MongoDB #Realtime #SystemDesign #DevOps #WebDevelopment #Metaverse #OpenSource&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30192/coder-legion-dev-log-building-the-myzubster-metaverse-from-the-infrastructure-up</guid>
<pubDate>Thu, 08 Oct 2026 02:32:06 +0000</pubDate>
</item>
<item>
<title>Your Zod Schema's .optional() Might Be Silently Rejecting Valid Data From MongoDB</title>
<link>https://coderlegion.com/29666/your-zod-schemas-optional-might-be-silently-rejecting-valid-data-from-mongodb</link>
<description>&lt;p&gt;This one trips people up specifically because the two words feel interchangeable in everyday English, &quot;optional&quot; and &quot;nullable&quot; both sound like &quot;this might not be there,&quot; and Zod treats them as two genuinely distinct, non-overlapping conditions.&lt;/p&gt;
&lt;h3 id=&quot;what-each-one-actually-validates&quot;&gt;What Each One Actually Validates&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { z } from 'zod';

const Schema = z.object({
  bio: z.string().optional(), // allows the key to be MISSING, or a string. Does NOT allow null.
  avatar: z.string().nullable(), // requires the key to be PRESENT, as a string OR null. Rejects missing.
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.optional()&lt;/code&gt; accepts &lt;code&gt;undefined&lt;/code&gt;, meaning the field can be genuinely absent from the object entirely, but if it's present, it must match the base type, never &lt;code&gt;null&lt;/code&gt;. &lt;code&gt;.nullable()&lt;/code&gt; accepts &lt;code&gt;null&lt;/code&gt; specifically, but the key itself still needs to actually be present in the object, just potentially holding the value &lt;code&gt;null&lt;/code&gt; rather than a real value. These aren't two flavors of the same leniency, they're validating two different, non-overlapping conditions, and passing the wrong kind of absence to either one fails validation.&lt;/p&gt;
&lt;h3 id=&quot;where-this-actually-breaks-with-mongodb-specifically&quot;&gt;Where This Actually Breaks With MongoDB Specifically&lt;/h3&gt;
&lt;p&gt;MongoDB documents very commonly store an explicit &lt;code&gt;null&lt;/code&gt; for a field that has no value, rather than omitting the key entirely, especially for fields that were part of the original schema design but just haven't been set for a given document yet.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// A real document from MongoDB
{
  _id: '...',
  name: 'Alex',
  bio: null, // explicitly null, not missing
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// ❌ This schema expects the key to be absent, not present-and-null
const UserSchema = z.object({
  name: z.string(),
  bio: z.string().optional(),
});

UserSchema.safeParse(userFromDb); // fails: bio received null, optional() doesn't accept null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;bio: null&lt;/code&gt; is a completely valid, common shape coming straight out of MongoDB, and &lt;code&gt;.optional()&lt;/code&gt; rejects it anyway, since &lt;code&gt;.optional()&lt;/code&gt; only ever expected &lt;code&gt;undefined&lt;/code&gt;, never &lt;code&gt;null&lt;/code&gt;. The validation failure here isn't catching a real data problem, it's a mismatch between what Zod's &lt;code&gt;.optional()&lt;/code&gt; actually means and what the developer assumed it meant when writing the schema against real database output.&lt;/p&gt;
&lt;h3 id=&quot;the-fix-match-the-schema-to-what-the-data-actually-does&quot;&gt;The Fix: Match the Schema to What the Data Actually Does&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const UserSchema = z.object({
  name: z.string(),
  bio: z.string().nullable(), // correctly matches MongoDB's explicit null
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a field that MongoDB stores as explicit &lt;code&gt;null&lt;/code&gt; when empty, &lt;code&gt;.nullable()&lt;/code&gt; is the correct validator, not &lt;code&gt;.optional()&lt;/code&gt;. If a field could genuinely be either missing entirely or explicitly null, both conditions need to be allowed together:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;bio: z.string().nullable().optional(), // accepts a string, null, OR a missing key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Chaining both is completely valid and often the most accurate reflection of real-world data that's passed through several code paths, some of which omit the key, others of which explicitly set it to &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;why-this-doesn-t-show-up-until-real-data-hits-it&quot;&gt;Why This Doesn't Show Up Until Real Data Hits It&lt;/h3&gt;
&lt;p&gt;A schema tested only against hand-written test objects during development, where the developer naturally omits fields they don't care about rather than explicitly setting them to &lt;code&gt;null&lt;/code&gt;, never actually exercises this gap. The validation failure only shows up once real documents, containing MongoDB's genuine, common pattern of explicit &lt;code&gt;null&lt;/code&gt; values, flow through the same schema, often well after the schema was written and seemingly working correctly.&lt;/p&gt;
&lt;h3 id=&quot;a-quick-way-to-check-which-one-you-actually-need&quot;&gt;A Quick Way to Check Which One You Actually Need&lt;/h3&gt;
&lt;p&gt;Query a real, representative document from your actual collection and look at the field in question directly. If it shows up as &lt;code&gt;null&lt;/code&gt; in the raw document, you need &lt;code&gt;.nullable()&lt;/code&gt;. If the key is genuinely absent from documents where it has no value, you need &lt;code&gt;.optional()&lt;/code&gt;. Guessing based on the field's name or what &quot;feels right&quot; is exactly how this mismatch gets introduced in the first place, checking the real data removes the ambiguity entirely.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const doc = await User.findOne({}).lean();
console.log(Object.keys(doc).includes('bio'), doc.bio);
// If bio is a key with value null → nullable()
// If bio is simply absent as a key → optional()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-actual-rule&quot;&gt;The Actual Rule&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;.optional()&lt;/code&gt; and &lt;code&gt;.nullable()&lt;/code&gt; are not interchangeable leniency settings, they validate two distinct, specific shapes of absence, and MongoDB's own default behavior of storing explicit &lt;code&gt;null&lt;/code&gt; means &lt;code&gt;.nullable()&lt;/code&gt; is very often the one you actually need for fields coming straight out of a database, not &lt;code&gt;.optional()&lt;/code&gt;, even though &quot;optional&quot; is the word that intuitively comes to mind first.&lt;/strong&gt; When a schema seems to be rejecting data that looks completely fine in your database, this mismatch is one of the first things worth checking.&lt;/p&gt;
&lt;p&gt;I ran into exactly this building the MongoDB-backed dashboards covered in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.pixelanas.com/blog/mongodb-mongoose-patterns-2026&quot;&gt;MongoDB patterns I use in every Next.js project&lt;/a&gt;, and it's one of those small schema details worth getting right once rather than re-discovering on every new project.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If a Zod schema is rejecting data that looks completely valid sitting in your actual database, check whether it's this exact &lt;code&gt;.optional()&lt;/code&gt; versus &lt;code&gt;.nullable()&lt;/code&gt; mismatch before assuming the data itself is somehow wrong. Drop what you find in the comments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29666/your-zod-schemas-optional-might-be-silently-rejecting-valid-data-from-mongodb</guid>
<pubDate>Sat, 03 Oct 2026 21:14:32 +0000</pubDate>
</item>
<item>
<title>generateMetadata and Your Page Component Are Probably Querying the Same Data Twice</title>
<link>https://coderlegion.com/27732/generatemetadata-and-your-page-component-are-probably-querying-the-same-data-twice</link>
<description>&lt;p&gt;I wrote earlier about &lt;code&gt;cache()&lt;/code&gt; fixing redundant database queries when multiple components in a tree independently call the same query function. There's one specific, extremely common place this exact pattern shows up that's worth calling out directly, because it's not an edge case, it's the default shape of nearly every dynamic page with SEO metadata.&lt;/p&gt;
&lt;h3 id=&quot;the-setup-almost-every-dynamic-page-has&quot;&gt;The Setup Almost Every Dynamic Page Has&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/blog/[slug]/page.tsx
import { getPost } from '@/lib/queries/posts';

export async function generateMetadata({ params }) {
  const { slug } = await params;
  const post = await getPost(slug); // query #1

  return {
    title: post.title,
    description: post.excerpt,
  };
}

export default async function BlogPost({ params }) {
  const { slug } = await params;
  const post = await getPost(slug); // query #2, same post, same request

  return (
    &amp;lt;article&amp;gt;
      &amp;lt;h1&amp;gt;{post.title}&amp;lt;/h1&amp;gt;
      &amp;lt;div&amp;gt;{post.content}&amp;lt;/div&amp;gt;
    &amp;lt;/article&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the completely standard pattern from the Next.js metadata setup covered in an earlier post, and it's genuinely necessary, &lt;code&gt;generateMetadata&lt;/code&gt; needs the post's title and excerpt for the meta tags, the page component needs the post's full content to actually render it. Both run for every single request to this page. Both call &lt;code&gt;getPost(slug)&lt;/code&gt; independently. Without a specific fix, that's two full database round trips for what is, semantically, the exact same piece of data, fetched twice, for every single page view.&lt;/p&gt;
&lt;h3 id=&quot;why-this-specific-case-matters-more-than-it-might-seem&quot;&gt;Why This Specific Case Matters More Than It Might Seem&lt;/h3&gt;
&lt;p&gt;This isn't a rare pattern someone might occasionally write, it's the default shape of &lt;code&gt;generateMetadata&lt;/code&gt; combined with any dynamic page, which means it's quietly doubling database load on nearly every content-driven page across an entire site, blog posts, product pages, any dynamically rendered detail page with SEO metadata. A site with meaningful traffic on these pages is paying for twice the database queries it actually needs, on some of its most frequently visited pages, for no functional reason.&lt;/p&gt;
&lt;h3 id=&quot;the-fix-wrap-the-query-in-react-s-cache-&quot;&gt;The Fix: Wrap the Query in React's cache()&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// lib/queries/posts.ts
import { cache } from 'react';
import { connectDB } from '@/lib/db';
import Post from '@/models/Post';

export const getPost = cache(async (slug: string) =&amp;gt; {
  await connectDB();
  return Post.findOne({ slug }).lean();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's the entire fix, no changes needed anywhere else. &lt;code&gt;generateMetadata&lt;/code&gt; and the page component both still call &lt;code&gt;getPost(slug)&lt;/code&gt; exactly as before, the code looks identical. What changes is that React's request memoization now recognizes both calls, same function, same argument, within the same render pass, and only actually executes the underlying query once, returning the cached result for the second call.&lt;/p&gt;
&lt;h3 id=&quot;why-this-is-worth-checking-specifically-not-just-generally&quot;&gt;Why This Is Worth Checking Specifically, Not Just Generally&lt;/h3&gt;
&lt;p&gt;The earlier post on this topic covered the general pattern, multiple components in a tree calling the same query. This specific case is worth checking on its own because it's so easy to miss precisely because &lt;code&gt;generateMetadata&lt;/code&gt; and the page component don't look like they're part of the same &quot;component tree&quot; in the way nested components obviously are. They're two separate exported functions in the same file, and it's genuinely easy to write both, each looking completely correct in isolation, without ever noticing they're duplicating a database call for the same request.&lt;/p&gt;
&lt;h3 id=&quot;a-quick-way-to-verify-this-is-actually-happening&quot;&gt;A Quick Way to Verify This Is Actually Happening&lt;/h3&gt;
&lt;p&gt;Add a temporary log inside the un-cached query function and load the page once:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export async function getPost(slug: string) {
  console.log('getPost called for:', slug); // temporary
  await connectDB();
  return Post.findOne({ slug }).lean();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see that log line twice for a single page load, that's this exact issue, confirmed directly rather than assumed. After wrapping the function in &lt;code&gt;cache()&lt;/code&gt;, the same test should show it logging only once per request.&lt;/p&gt;
&lt;h3 id=&quot;where-this-doesn-t-apply&quot;&gt;Where This Doesn't Apply&lt;/h3&gt;
&lt;p&gt;If &lt;code&gt;generateMetadata&lt;/code&gt; and the page component genuinely need different data, metadata pulling from a lighter, separate summary endpoint while the page fetches full content from somewhere else entirely, there's no duplication to fix, since they're not actually calling the same underlying function with the same arguments in the first place. This fix specifically applies when both are calling the identical query function, which, for most blog and content-driven page setups, is exactly the common case.&lt;/p&gt;
&lt;h3 id=&quot;the-actual-rule-1&quot;&gt;The Actual Rule&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Any query function called both inside &lt;code&gt;generateMetadata&lt;/code&gt; and inside the page component it belongs to should be wrapped in &lt;code&gt;cache()&lt;/code&gt;, as close to a universal default as a rule gets in this specific area.&lt;/strong&gt; It costs nothing to add, changes no calling code, and directly eliminates a real, silent, doubled database query that exists by default on essentially every dynamically rendered page with SEO metadata.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Go check any page in your own project using &lt;code&gt;generateMetadata&lt;/code&gt; alongside a matching page component, specifically whether the underlying query function is wrapped in &lt;code&gt;cache()&lt;/code&gt;. If it's not, you're very likely double-querying on every single page load for that route. Drop what you find in the comments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27732/generatemetadata-and-your-page-component-are-probably-querying-the-same-data-twice</guid>
<pubDate>Sat, 19 Sep 2026 10:48:58 +0000</pubDate>
</item>
<item>
<title>How I Eliminated 19,494 Duplicate MongoDB Records (And What I'd Do Differently Today)</title>
<link>https://coderlegion.com/27703/how-i-eliminated-19-494-duplicate-mongodb-records-and-what-id-do-differently-today</link>
<description>&lt;p&gt;At a previous organization, I was handed ownership of a job platform serving 1,000+ users worldwide. Jobs on the platform could be scoped to a single city, a state, a whole country, or &quot;available worldwide.&quot; Simple enough. At least, you'd think so.&lt;/p&gt;
&lt;p&gt;When I joined, this was early in my career and I was a frontend dev. I was interested in backend work but hadn't done any real backend engineering yet — I wasn't battle-tested. Somewhere along the line, I got handed the whole platform. Frontend, backend, all of it. Bracing moment. I forced myself to slow down, actually understand the system end to end, and build a real plan before touching anything.&lt;/p&gt;
&lt;p&gt;After talking with product management, I pulled up the backend task backlog. I learn best by doing, so I started at the bottom — smallest bugs first, working my way up. It went well for a while. Then I hit a ticket tied to core business logic.&lt;/p&gt;
&lt;h3 id=&quot;the-wake-up-call&quot;&gt;The Wake-Up Call&lt;/h3&gt;
&lt;p&gt;Some endpoints were clocking API response times north of &lt;strong&gt;7,000ms&lt;/strong&gt;. I genuinely thought it was a typo. Then I remembered running into something similar on the frontend side before — so I patched the UX so users never felt the full weight of the delay. But the actual problem was still sitting there, untouched.&lt;/p&gt;
&lt;p&gt;Before touching any code, I made a checklist. We were running MongoDB with NestJS:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What schemas was the endpoint touching?&lt;/li&gt;
&lt;li&gt;What optimization had already been applied to those schemas?&lt;/li&gt;
&lt;li&gt;How was the endpoint fetching data?&lt;/li&gt;
&lt;li&gt;How many records did each related schema currently hold?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Checklist in hand, I went into the codebase.&lt;/p&gt;
&lt;p&gt;My innocent dev brain was not prepared for the chaos I found.&lt;/p&gt;
&lt;h3 id=&quot;what-was-actually-happening&quot;&gt;What Was Actually Happening&lt;/h3&gt;
&lt;p&gt;Here's the setup: assume an empty &lt;code&gt;jobs&lt;/code&gt; collection. A recruiter creates a job listing scoped to &lt;strong&gt;New York City&lt;/strong&gt;. The backend takes the payload and creates one record with location set to New York City. Straightforward.&lt;/p&gt;
&lt;p&gt;Now assume another empty collection, and this time the job is scoped to the &lt;strong&gt;United States&lt;/strong&gt; instead of a single city. What do you think happens?&lt;/p&gt;
&lt;p&gt;If you guessed &quot;the same thing — one record, location set to United States&quot; — I have bad news for you.&lt;/p&gt;
&lt;p&gt;What the backend &lt;em&gt;actually&lt;/em&gt; did: it looked at the payload, recognized &quot;United States&quot; as a country rather than a city, looked up every known city in the U.S. — and created a separate job record for &lt;strong&gt;each one&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Read that again.&lt;/p&gt;
&lt;p&gt;Instead of one record, the platform generated one identical record per city, differing only in &lt;code&gt;_id&lt;/code&gt; and location. And it got worse: a job marked &quot;available worldwide&quot; would spawn a record for every city on the planet.&lt;/p&gt;
&lt;p&gt;This was a live product with real traffic. Querying that collection — even with indexes in place — was a nightmare.&lt;/p&gt;
&lt;p&gt;For context on scale: a single job scoped to just the USA generated &lt;strong&gt;19,495 records&lt;/strong&gt; — one canonical listing plus 19,494 exact duplicates.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Database records (1 USA-wide job) &lt;/td&gt;&lt;td&gt;19,495&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Exact duplicates among them &lt;/td&gt;&lt;td&gt;19,494&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Query time on affected endpoints &lt;/td&gt;&lt;td&gt;~2.3s (worst cases exceeded 7s for worldwide-scoped jobs)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;My first reaction was horrified confusion. After talking it over with management, I learned there &lt;em&gt;was&lt;/em&gt; a reason for the original design — I won't get into it here, it's not the point of this piece. What mattered was that I had free rein to fix it if I could pull it off. Hold my Pokémon card.&lt;/p&gt;
&lt;h3 id=&quot;the-constraints&quot;&gt;The Constraints&lt;/h3&gt;
&lt;p&gt;Fixing the architecture wasn't the hard part conceptually — the hard part was fixing it under real constraints:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We still needed to resolve &lt;em&gt;which&lt;/em&gt; cities a job was available in, for every existing feature that depended on that.&lt;/li&gt;
&lt;li&gt;Whatever I built had to slot into the existing infrastructure without a rewrite of everything downstream.&lt;/li&gt;
&lt;li&gt;I needed to show measurable, provable performance gains — not just &quot;trust me, it's better.&quot;&lt;/li&gt;
&lt;li&gt;None of this could disrupt daily active users. No maintenance windows, no downtime.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;designing-the-fix&quot;&gt;Designing the Fix&lt;/h3&gt;
&lt;p&gt;I started with the schema. The core question: how do I preserve full location fidelity while collapsing tens of thousands of records into one?&lt;/p&gt;
&lt;p&gt;I thought back to basic compression concepts — take data, strip it to a minimal representation, and pair it with the instructions needed to reconstruct the original. That's essentially what I needed here: a compact way to represent &quot;this job is available in X&quot; without one document per X.&lt;/p&gt;
&lt;p&gt;Two deliverables:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A new, efficient schema.&lt;/li&gt;
&lt;li&gt;A migration path that collapsed the old duplicated records into the new schema.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For the new schema, I kept every existing field and replaced the flat &lt;code&gt;location&lt;/code&gt; string with a structured &lt;code&gt;locationObj&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface LocationObj {
  country: string | string[];
  state: string | string[];
  city: string | string[];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The logic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single city&lt;/strong&gt; → &lt;code&gt;city&lt;/code&gt; is a string, &lt;code&gt;state&lt;/code&gt; and &lt;code&gt;country&lt;/code&gt; resolve to that city's parent state/country.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A specific group of cities&lt;/strong&gt; → &lt;code&gt;city&lt;/code&gt; becomes a string array.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An entire state&lt;/strong&gt; → &lt;code&gt;city&lt;/code&gt; becomes the sentinel value &lt;code&gt;&quot;all&quot;&lt;/code&gt;, &lt;code&gt;state&lt;/code&gt; holds the actual state name.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An entire country&lt;/strong&gt; → &lt;code&gt;state&lt;/code&gt; and &lt;code&gt;city&lt;/code&gt; both become &lt;code&gt;&quot;all&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Worldwide&lt;/strong&gt; → &lt;code&gt;country&lt;/code&gt;, &lt;code&gt;state&lt;/code&gt;, and &lt;code&gt;city&lt;/code&gt; all become &lt;code&gt;&quot;all&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So a job available across Berlin, Munich, and Hamburg looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;{
  title: &quot;Marketing Manager&quot;,
  company: &quot;TechCorp&quot;,
  locationObj: {
    country: &quot;Germany&quot;,
    state: &quot;all&quot;,
    city: [&quot;Berlin&quot;, &quot;Munich&quot;, &quot;Hamburg&quot;]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And a job available anywhere in the world:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;{
  title: &quot;Software Developer&quot;,
  company: &quot;TechCorp&quot;,
  locationObj: {
    country: &quot;all&quot;,
    state: &quot;all&quot;,
    city: &quot;all&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One document. Full location semantics preserved. No fan-out.&lt;/p&gt;
&lt;h3 id=&quot;migrating-without-downtime&quot;&gt;Migrating Without Downtime&lt;/h3&gt;
&lt;p&gt;The deduplication piece ran as a batch script against the old collection: group records that shared everything &lt;em&gt;except&lt;/em&gt; location (same title, company, description, salary — the actual &quot;identity&quot; of a job), reconstruct the correct &lt;code&gt;locationObj&lt;/code&gt; from the set of location values in that group, and write a single new document into the new collection.&lt;/p&gt;
&lt;p&gt;Once the new collection was validated against the old one, I built new endpoints on top of the new schema, mirroring the contracts of the old ones, and prepared the frontend to point at them.&lt;/p&gt;
&lt;p&gt;The cutover itself was a backfill with two catch-up passes, not a one-shot migration:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run the migration script once against the live old collection.&lt;/li&gt;
&lt;li&gt;Right before deploying the new endpoints, re-run it — this catches any jobs created &lt;em&gt;while&lt;/em&gt; the first pass was running, since the old collection kept accepting writes the entire time.&lt;/li&gt;
&lt;li&gt;Deploy the new endpoints and switch the frontend over.&lt;/li&gt;
&lt;li&gt;Re-run the script once more right after — this catches anything created under the old schema during the deploy window itself, in case the rollout wasn't instantaneous.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I tested the full path — frontend, backend, and DB — on local and staging before any of this touched production, so by the time it shipped, the swap itself was just a normal deploy. From a user's perspective, they kept seeing exactly the jobs they were supposed to see, the whole way through.&lt;/p&gt;
&lt;h3 id=&quot;querying-the-new-schema&quot;&gt;Querying the New Schema&lt;/h3&gt;
&lt;p&gt;The query logic has to walk the hierarchy — country, then state, then city — because &quot;all&quot; at any level short-circuits the rest:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const query = {
  $or: [
    { &quot;locationObj.country&quot;: &quot;all&quot; },
    { &quot;locationObj.country&quot;: userCountry, &quot;locationObj.state&quot;: &quot;all&quot; },
    { &quot;locationObj.country&quot;: userCountry, &quot;locationObj.state&quot;: userState, &quot;locationObj.city&quot;: &quot;all&quot; },
    { &quot;locationObj.country&quot;: userCountry, &quot;locationObj.city&quot;: userCity },
    { &quot;locationObj.country&quot;: userCountry, &quot;locationObj.city&quot;: { $in: [userCity] } }
  ]
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Combined with a compound index on &lt;code&gt;locationObj.country&lt;/code&gt;, &lt;code&gt;locationObj.state&lt;/code&gt;, and &lt;code&gt;locationObj.city&lt;/code&gt;, this scales linearly with the number of &lt;em&gt;actual&lt;/em&gt; jobs — not the number of cities they happen to be available in.&lt;/p&gt;
&lt;h3 id=&quot;the-results&quot;&gt;The Results&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Metric &lt;/th&gt;&lt;th&gt;Before &lt;/th&gt;&lt;th&gt;After &lt;/th&gt;&lt;th&gt;Change&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Database records (1 USA-wide job) &lt;/td&gt;&lt;td&gt;19,495 &lt;/td&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;99.99% fewer&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Query time &lt;/td&gt;&lt;td&gt;~2.3s &lt;/td&gt;&lt;td&gt;~0.12s &lt;/td&gt;&lt;td&gt;~95% faster&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Storage footprint &lt;/td&gt;&lt;td&gt;847 MB &lt;/td&gt;&lt;td&gt;0.043 MB &lt;/td&gt;&lt;td&gt;99.99% smaller&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Index size &lt;/td&gt;&lt;td&gt;156 MB &lt;/td&gt;&lt;td&gt;0.008 MB &lt;/td&gt;&lt;td&gt;99.99% smaller&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Memory usage &lt;/td&gt;&lt;td&gt;~4.2 GB &lt;/td&gt;&lt;td&gt;~0.21 MB &lt;/td&gt;&lt;td&gt;99.99% less RAM&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Exact duplicate documents &lt;/td&gt;&lt;td&gt;19,494 &lt;/td&gt;&lt;td&gt;0 &lt;/td&gt;&lt;td&gt;Eliminated&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Management was, unsurprisingly, impressed.&lt;/p&gt;
&lt;h3 id=&quot;how-i-d-do-it-now&quot;&gt;How I'd Do It Now&lt;/h3&gt;
&lt;p&gt;Looking back at this with a few more years under my belt, the schema still holds up. What I'd change is the migration and verification process — at the time, &quot;backfill, then re-run twice around the cutover&quot; felt rigorous. It worked. But it also relied on timing more than I'd want to admit if I were reviewing this today.&lt;/p&gt;
&lt;p&gt;The more rigid version of this — the one I'd actually reach for now — is the &lt;strong&gt;expand-contract pattern&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Expand&lt;/strong&gt;: Deploy dual-writes behind a feature flag — every job creation or update writes to both the old and new schema at the same time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sync&lt;/strong&gt;: Use MongoDB Change Streams to mirror any old-schema writes into the new schema in real time during the transition, instead of relying on timed reruns of a migration script. This closes the race-condition window entirely — there's no gap to miss, because nothing is waiting for a scheduled pass.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contract&lt;/strong&gt;: Once reads are fully on the new schema and stable, kill the old write path.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I'd also make the migration script itself &lt;strong&gt;idempotent and checkpointed&lt;/strong&gt; — track the last processed &lt;code&gt;_id&lt;/code&gt; so a crash mid-run means it resumes instead of reprocessing or silently skipping documents. Mine was a script I babysat through completion; a checkpointed version is one I could walk away from.&lt;/p&gt;
&lt;p&gt;On verification — &quot;compare the counts&quot; was good enough to convince management, but it wasn't rigorous. I'd now do a real reconciliation pass: diff the reconstructed &lt;code&gt;locationObj&lt;/code&gt; against the actual set of cities in the old schema, per job, and flag any mismatch automatically instead of trusting a spot check. And before flipping reads over, I'd run a dark-launch period — querying both schemas in production and logging discrepancies without serving the shadow result to users — so any gap shows up before it's user-facing, not after.&lt;/p&gt;
&lt;p&gt;One more thing I'd change that isn't about the migration at all: I'd want this caught before it ever shipped. The root failure was that nothing stopped a single job-creation event from silently producing thousands of documents. A write-layer guardrail — &quot;creating one job should never result in more than one document, alert if it does&quot; — plus a five-line regression test asserting exactly that, would have caught this in review instead of in production months later.&lt;/p&gt;
&lt;h3 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Embedding vs. referencing isn't the real question — normalization is.&lt;/strong&gt; The original design wasn't wrong because it embedded or referenced incorrectly; it was wrong because it materialized every possible resolution of a hierarchical value instead of storing the hierarchy itself. The general MongoDB advice of &quot;embed for read-heavy, reference for write-heavy&quot; didn't apply here — the fix was closer to a compression problem than a modeling problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sentinel values are underrated.&lt;/strong&gt; Using &lt;code&gt;&quot;all&quot;&lt;/code&gt; as a stand-in for &quot;every value at this level&quot; let one field type (&lt;code&gt;string | string[]&lt;/code&gt;) represent five distinct scopes without a single boolean flag or separate &lt;code&gt;scope&lt;/code&gt; enum.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the actual scale of the problem before designing the fix.&lt;/strong&gt; I didn't just need &quot;make it faster&quot; — I needed a number to prove it. Knowing it was 19,495 records for &lt;em&gt;one job&lt;/em&gt; is what made the case to management, and it's what makes this story worth telling.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Fields, numbers, and code above are reconstructed from a project where I owned the full backend under an NDA — details are generalized where necessary, but the architecture, the failure mode, and the metrics are real.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Cover photo by &lt;a rel=&quot;nofollow&quot; href=&quot;https://unsplash.com/@kommumikation?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Mika Baumeister&lt;/a&gt; on &lt;a rel=&quot;nofollow&quot; href=&quot;https://unsplash.com/photos/white-printing-paper-with-numbers-Wpnoqo2plFA?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/27703/how-i-eliminated-19-494-duplicate-mongodb-records-and-what-id-do-differently-today</guid>
<pubDate>Sat, 19 Sep 2026 08:33:15 +0000</pubDate>
</item>
<item>
<title>Building MyZubster: A Circular Marketplace Connected to a Social Metaverse</title>
<link>https://coderlegion.com/27164/building-myzubster-a-circular-marketplace-connected-to-a-social-metaverse</link>
<description>&lt;p&gt;Building MyZubster: A Circular Marketplace Connected to a Social Metaverse&lt;br&gt;
MyZubster is an open-source ecosystem that connects a circular marketplace, local communities, educational support, underground culture, visual storytelling and an experimental social metaverse.&lt;br&gt;
The project is based on a cyclical model:&lt;br&gt;
Discover → Connect → Exchange → Use → Learn → Repair → Reuse → Recover → Contribute&lt;br&gt;
Instead of building another isolated marketplace or virtual world, MyZubster connects these activities through shared navigation, account-linked characters and measurable user journeys.&lt;br&gt;
The MyZubster Marketplace&lt;br&gt;
The Marketplace is the operational discovery layer of the ecosystem. It organizes products, services, skills and community activities into accessible categories and subcategories.&lt;br&gt;
The current demo explores areas including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;local food and independent producers;&lt;/li&gt;
&lt;li&gt;kefir and fermented products;&lt;/li&gt;
&lt;li&gt;underground music and alternative culture;&lt;/li&gt;
&lt;li&gt;sound-system equipment;&lt;/li&gt;
&lt;li&gt;event and creative services;&lt;/li&gt;
&lt;li&gt;university and educational assistance;&lt;/li&gt;
&lt;li&gt;programming and digital services;&lt;/li&gt;
&lt;li&gt;repair and reuse;&lt;/li&gt;
&lt;li&gt;recycling and material recovery;&lt;/li&gt;
&lt;li&gt;sustainable everyday products.&lt;br&gt;
Explore the Marketplace:&lt;br&gt;
🛒 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/marketplace?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=marketplace_overview&quot;&gt;https://www.myzubster.com/marketplace?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=marketplace_overview&lt;/a&gt;&lt;br&gt;
Why Kefir Is Part of the Ecosystem&lt;br&gt;
Kefir is a useful example of how a small local product can become part of a larger circular process.&lt;br&gt;
A visitor can discover a producer, learn about the production process, contact the seller and understand what happens to the packaging after consumption.&lt;br&gt;
Local production&lt;br&gt;
  ↓&lt;br&gt;
Marketplace discovery&lt;br&gt;
  ↓&lt;br&gt;
Informed purchase&lt;br&gt;
  ↓&lt;br&gt;
Consumption&lt;br&gt;
  ↓&lt;br&gt;
Container reuse&lt;br&gt;
  ↓&lt;br&gt;
Recycling or material recovery&lt;br&gt;
MyZubster does not automatically certify food safety, production standards or health benefits. Those claims require evidence and compliance from the responsible producer.&lt;br&gt;
The platform’s role is to make sellers, information and provenance easier to discover and inspect.&lt;br&gt;
Open the local-products Marketplace funnel:&lt;br&gt;
🌱 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/marketplace?category=food&amp;amp;utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=kefir_local_producers&quot;&gt;https://www.myzubster.com/marketplace?category=food&amp;amp;utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=kefir_local_producers&lt;/a&gt;&lt;br&gt;
Supporting Underground and Independent Culture&lt;br&gt;
Underground communities often depend on informal networks connecting musicians, artists, technicians, venues and equipment owners.&lt;br&gt;
MyZubster can provide optional infrastructure for discovering:&lt;/li&gt;
&lt;li&gt;DJs, musicians and independent performers;&lt;/li&gt;
&lt;li&gt;sound-system equipment;&lt;/li&gt;
&lt;li&gt;audio repair specialists;&lt;/li&gt;
&lt;li&gt;rehearsal and event services;&lt;/li&gt;
&lt;li&gt;visual artists and graphic designers;&lt;/li&gt;
&lt;li&gt;alternative clothing and records;&lt;/li&gt;
&lt;li&gt;independent venues and cultural initiatives;&lt;/li&gt;
&lt;li&gt;reusable creative materials.&lt;br&gt;
A sound system can move through a complete circular lifecycle:&lt;br&gt;
Equipment discovery&lt;br&gt;
  ↓&lt;br&gt;
Purchase or rental&lt;br&gt;
  ↓&lt;br&gt;
Event use&lt;br&gt;
  ↓&lt;br&gt;
Maintenance&lt;br&gt;
  ↓&lt;br&gt;
Repair&lt;br&gt;
  ↓&lt;br&gt;
Resale and reuse&lt;br&gt;
  ↓&lt;br&gt;
Component recovery&lt;br&gt;
The objective is not to control underground culture. It is to give independent communities tools they can use on their own terms.&lt;br&gt;
Explore the underground and sound-system funnel:&lt;br&gt;
🔊 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/marketplace?category=sound-system&amp;amp;utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=underground_sound_system&quot;&gt;https://www.myzubster.com/marketplace?category=sound-system&amp;amp;utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=underground_sound_system&lt;/a&gt;&lt;br&gt;
University and Educational Assistance&lt;br&gt;
Education is another important part of the MyZubster cycle.&lt;br&gt;
Students can discover legitimate support such as:&lt;/li&gt;
&lt;li&gt;tutoring;&lt;/li&gt;
&lt;li&gt;programming guidance;&lt;/li&gt;
&lt;li&gt;language practice;&lt;/li&gt;
&lt;li&gt;research-method explanations;&lt;/li&gt;
&lt;li&gt;document and presentation formatting;&lt;/li&gt;
&lt;li&gt;portfolio support;&lt;/li&gt;
&lt;li&gt;study groups;&lt;/li&gt;
&lt;li&gt;guidance toward internships;&lt;/li&gt;
&lt;li&gt;open-source contribution opportunities.&lt;br&gt;
Educational support should help students learn. It must not be used to impersonate students, complete prohibited examinations or submit deceptive academic work.&lt;br&gt;
The educational cycle is:&lt;br&gt;
Ask for support&lt;br&gt;
  ↓&lt;br&gt;
Learn&lt;br&gt;
  ↓&lt;br&gt;
Practise&lt;br&gt;
  ↓&lt;br&gt;
Build a project&lt;br&gt;
  ↓&lt;br&gt;
Document the work&lt;br&gt;
  ↓&lt;br&gt;
Contribute&lt;br&gt;
  ↓&lt;br&gt;
Help another learner&lt;br&gt;
Explore the educational Marketplace funnel:&lt;br&gt;
🎓 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/marketplace?category=education&amp;amp;utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=university_help&quot;&gt;https://www.myzubster.com/marketplace?category=education&amp;amp;utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=university_help&lt;/a&gt;&lt;br&gt;
Entering the MyZubster Metaverse&lt;br&gt;
The MyZubster Metaverse is the visual and social navigation layer of the ecosystem.&lt;br&gt;
Its current environment, Neon Plaza, allows visitors to:&lt;/li&gt;
&lt;li&gt;enter as guests or authenticated users;&lt;/li&gt;
&lt;li&gt;use account-linked characters;&lt;/li&gt;
&lt;li&gt;move around a shared environment;&lt;/li&gt;
&lt;li&gt;see other active visitors;&lt;/li&gt;
&lt;li&gt;communicate through chat and emotes;&lt;/li&gt;
&lt;li&gt;discover nearby participants;&lt;/li&gt;
&lt;li&gt;visit ecosystem portals;&lt;/li&gt;
&lt;li&gt;complete an exploration mission;&lt;/li&gt;
&lt;li&gt;preserve visited-zone progress;&lt;/li&gt;
&lt;li&gt;discover experimental virtual rooms.&lt;br&gt;
Enter Neon Plaza:&lt;br&gt;
🪐 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/metaverse?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=metaverse_entry&quot;&gt;https://www.myzubster.com/metaverse?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=metaverse_entry&lt;/a&gt;&lt;br&gt;
Account-Linked Characters&lt;br&gt;
A visitor can explore without creating a verified identity claim.&lt;br&gt;
Authenticated users can use persistent account-linked characters. The server determines which character belongs to the authenticated account instead of trusting a name supplied by the browser.&lt;br&gt;
The current system includes:&lt;/li&gt;
&lt;li&gt;canonical server-side character profiles;&lt;/li&gt;
&lt;li&gt;account-linked character ownership;&lt;/li&gt;
&lt;li&gt;authenticated profile retrieval;&lt;/li&gt;
&lt;li&gt;server-backed mission progress;&lt;/li&gt;
&lt;li&gt;visited-zone persistence;&lt;/li&gt;
&lt;li&gt;live player presence;&lt;/li&gt;
&lt;li&gt;session recovery;&lt;/li&gt;
&lt;li&gt;expired-JWT handling;&lt;/li&gt;
&lt;li&gt;protected character-progress updates.&lt;br&gt;
A display name is not considered legal identity evidence. The MYZ VERIFIED label indicates that the character is linked to a MyZubster account, not that a person’s legal identity, professional skills or qualifications have been certified.&lt;br&gt;
Mission Progress&lt;br&gt;
Visitors receive an initial mission: move through Neon Plaza and reach one of its portals.&lt;br&gt;
For guests, progress remains local to the browser. For authenticated characters, visited zones can be stored on the account-linked server profile.&lt;br&gt;
The server accepts only known landmark identifiers. It derives character ownership from the verified authentication token and does not trust a character name submitted by the client.&lt;br&gt;
This creates a safer progression model:&lt;br&gt;
Authenticated account&lt;br&gt;
  ↓&lt;br&gt;
Canonical character&lt;br&gt;
  ↓&lt;br&gt;
Visit a valid landmark&lt;br&gt;
  ↓&lt;br&gt;
Server validates the landmark&lt;br&gt;
  ↓&lt;br&gt;
Progress is attached to the account-owned character&lt;br&gt;
Try the mission funnel:&lt;br&gt;
🧭 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/metaverse?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=first_mission&quot;&gt;https://www.myzubster.com/metaverse?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=first_mission&lt;/a&gt;&lt;br&gt;
Experimental Virtual Rooms&lt;br&gt;
MyZubster is beginning to connect Neon Plaza with persistent virtual rooms.&lt;br&gt;
The discovery system applies access rules on the server:&lt;/li&gt;
&lt;li&gt;guests can discover public rooms;&lt;/li&gt;
&lt;li&gt;authenticated users can also discover account-only rooms;&lt;/li&gt;
&lt;li&gt;private rooms remain hidden;&lt;/li&gt;
&lt;li&gt;drafts and archived rooms are excluded;&lt;/li&gt;
&lt;li&gt;only published, scheduled or live spaces are discoverable.&lt;br&gt;
These rooms are still experimental. A dedicated room experience will be introduced only after navigation, access control, moderation and lifecycle handling are ready.&lt;br&gt;
Follow the virtual-room funnel:&lt;br&gt;
🏛️ &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/metaverse?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=virtual_rooms&quot;&gt;https://www.myzubster.com/metaverse?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=virtual_rooms&lt;/a&gt;&lt;br&gt;
MyZubster Visual&lt;br&gt;
MyZubster Visual provides the storytelling layer.&lt;br&gt;
Illustrations, interactive popups and visual narratives can explain connections between:&lt;/li&gt;
&lt;li&gt;physical products;&lt;/li&gt;
&lt;li&gt;circular lifecycles;&lt;/li&gt;
&lt;li&gt;environmental projects;&lt;/li&gt;
&lt;li&gt;characters;&lt;/li&gt;
&lt;li&gt;community contributions;&lt;/li&gt;
&lt;li&gt;Marketplace categories;&lt;/li&gt;
&lt;li&gt;Metaverse destinations.&lt;br&gt;
A popup can introduce a story, provide additional context and direct a visitor toward the next relevant part of the ecosystem.&lt;br&gt;
The intended funnel is:&lt;br&gt;
Visual story&lt;br&gt;
  ↓&lt;br&gt;
Interactive popup&lt;br&gt;
  ↓&lt;br&gt;
Context and explanation&lt;br&gt;
  ↓&lt;br&gt;
Marketplace category or project&lt;br&gt;
  ↓&lt;br&gt;
Metaverse exploration&lt;br&gt;
  ↓&lt;br&gt;
Community participation&lt;br&gt;
Explore the visual-story funnel:&lt;br&gt;
🎨 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/fumetto?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=visual_popups&quot;&gt;https://www.myzubster.com/fumetto?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=visual_popups&lt;/a&gt;&lt;br&gt;
How the Complete Circular Funnel Works&lt;br&gt;
Consider a visitor interested in locally produced kefir:&lt;ol&gt;
&lt;li&gt;They discover MyZubster through this article.&lt;/li&gt;
&lt;li&gt;They open the Marketplace.&lt;/li&gt;
&lt;li&gt;They explore local food and producer listings.&lt;/li&gt;
&lt;li&gt;They learn about the product and its provenance.&lt;/li&gt;
&lt;li&gt;They contact or purchase from an authorized seller.&lt;/li&gt;
&lt;li&gt;They reuse or correctly recycle the packaging.&lt;/li&gt;
&lt;li&gt;They enter the Metaverse.&lt;/li&gt;
&lt;li&gt;They visit the Marketplace or LIFE Projects portal.&lt;/li&gt;
&lt;li&gt;They document or share a useful contribution.&lt;/li&gt;
&lt;li&gt;That contribution helps another visitor discover the circular pathway.&lt;br&gt;
The same model can apply to underground sound equipment:&lt;/li&gt;
&lt;li&gt;Discover equipment.&lt;/li&gt;
&lt;li&gt;Use it for an event.&lt;/li&gt;
&lt;li&gt;Maintain and repair it.&lt;/li&gt;
&lt;li&gt;Resell or reuse it.&lt;/li&gt;
&lt;li&gt;Recover components at the end of its usable life.&lt;/li&gt;
&lt;li&gt;Share repair knowledge with the community.&lt;br&gt;
It can also apply to educational support:&lt;/li&gt;
&lt;li&gt;Request tutoring.&lt;/li&gt;
&lt;li&gt;Learn a skill.&lt;/li&gt;
&lt;li&gt;Build a project.&lt;/li&gt;
&lt;li&gt;Publish the code.&lt;/li&gt;
&lt;li&gt;Contribute to MyZubster.&lt;/li&gt;
&lt;li&gt;Help another learner.&lt;br&gt;
Technical Architecture&lt;br&gt;
The current implementation combines:&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;React for the user interface;&lt;/li&gt;
&lt;li&gt;Node.js and Express for backend services;&lt;/li&gt;
&lt;li&gt;MongoDB for persistent characters, progress and room records;&lt;/li&gt;
&lt;li&gt;authenticated API routes;&lt;/li&gt;
&lt;li&gt;server-authoritative access rules;&lt;/li&gt;
&lt;li&gt;short-lived presence and chat records;&lt;/li&gt;
&lt;li&gt;GitHub for open-source development;&lt;/li&gt;
&lt;li&gt;Vercel for deployment and web analytics.&lt;br&gt;
The architecture maintains an important boundary:&lt;br&gt;
The client presents the experience, but the server controls identity, ownership, persistence and access decisions.&lt;br&gt;
Measuring the Funnel&lt;br&gt;
Each link in this article contains UTM parameters designed to distinguish traffic from Coder Legion.&lt;br&gt;
The campaign uses:&lt;br&gt;
utm_source=coderlegion&lt;br&gt;
utm_medium=article&lt;br&gt;
utm_campaign=myzubster_circular_funnel&lt;br&gt;
The utm_content value identifies the selected journey:&lt;/li&gt;
&lt;li&gt;marketplace_overview&lt;/li&gt;
&lt;li&gt;kefir_local_producers&lt;/li&gt;
&lt;li&gt;underground_sound_system&lt;/li&gt;
&lt;li&gt;university_help&lt;/li&gt;
&lt;li&gt;metaverse_entry&lt;/li&gt;
&lt;li&gt;first_mission&lt;/li&gt;
&lt;li&gt;virtual_rooms&lt;/li&gt;
&lt;li&gt;visual_popups&lt;/li&gt;
&lt;li&gt;github_repository&lt;br&gt;
This makes it possible to compare which concepts attract attention and which destinations visitors choose next.&lt;br&gt;
Build With Us&lt;br&gt;
MyZubster is being developed openly. Developers can inspect the implementation, review the architecture, propose improvements and contribute code.&lt;br&gt;
💻 &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=github_repository&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=github_repository&lt;/a&gt;&lt;br&gt;
The project remains an evolving prototype. Experimental functionality, verified evidence and operational services are kept clearly distinguishable.&lt;br&gt;
The objective is not simply to create another marketplace or virtual environment. It is to build a connected system in which software helps communities discover opportunities, learn, exchange value, extend product lifecycles and create new contributions.&lt;br&gt;
Explore the Ecosystem&lt;br&gt;
🛒 Marketplace Demo&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/marketplace?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=marketplace_overview&quot;&gt;https://www.myzubster.com/marketplace?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=marketplace_overview&lt;/a&gt;&lt;br&gt;
🪐 MyZubster Metaverse&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/metaverse?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=metaverse_entry&quot;&gt;https://www.myzubster.com/metaverse?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=metaverse_entry&lt;/a&gt;&lt;br&gt;
🎨 MyZubster Visual&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/fumetto?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=visual_popups&quot;&gt;https://www.myzubster.com/fumetto?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=visual_popups&lt;/a&gt;&lt;br&gt;
💻 Open-source Repository&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=github_repository&quot;&gt;https://github.com/MyZubster-Ecosystem/myzubster?utm_source=coderlegion&amp;amp;utm_medium=article&amp;amp;utm_campaign=myzubster_circular_funnel&amp;amp;utm_content=github_repository&lt;/a&gt;&lt;h2 id=&quot;myzubster-opensource-webdevelopment-metaverse-marketplace-circulareconomy-reactjs-nodejs-mongodb-buildinpublic&quot;&gt;MyZubster #OpenSource #WebDevelopment #Metaverse #Marketplace #CircularEconomy #ReactJS #NodeJS #MongoDB #BuildInPublic&lt;/h2&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27164/building-myzubster-a-circular-marketplace-connected-to-a-social-metaverse</guid>
<pubDate>Mon, 14 Sep 2026 12:43:38 +0000</pubDate>
</item>
<item>
<title>That .lean() Query Optimization Might Be Leaking Password Hashes in Your API Response</title>
<link>https://coderlegion.com/26819/that-lean-query-optimization-might-be-leaking-password-hashes-in-your-api-response</link>
<description>&lt;p&gt;I've recommended &lt;code&gt;.lean()&lt;/code&gt; in earlier posts, it's genuinely the right call for read-heavy queries, faster, lighter, no Mongoose document overhead. There's a real gap it opens up that's worth calling out specifically, because it silently undoes a common, otherwise-good security pattern, and the failure is invisible unless you specifically go check the actual API response.&lt;/p&gt;
&lt;h3 id=&quot;the-setup-that-looks-like-a-solid-security-pattern&quot;&gt;The Setup That Looks Like a Solid Security Pattern&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// models/User.ts
const UserSchema = new Schema({
  name: String,
  email: String,
  password: { type: String, select: false }, // excluded from queries by default
}, {
  toJSON: {
    transform: (doc, ret) =&amp;gt; {
      delete ret.password; // extra safety net, strips it even if somehow selected
      delete ret.__v;
      return ret;
    },
  },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a reasonable, defense-in-depth setup, &lt;code&gt;select: false&lt;/code&gt; keeps the password out of normal queries by default, and the &lt;code&gt;toJSON&lt;/code&gt; transform is a second safety net stripping it from anything that does get serialized, in case it was ever explicitly selected somewhere. Two layers, looks solid.&lt;/p&gt;
&lt;h3 id=&quot;where-this-quietly-breaks&quot;&gt;Where This Quietly Breaks&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// app/api/users/route.ts
export async function GET() {
  const users = await User.find().select('+password').lean();
  // some legitimate internal reason to select password here, comparing hashes, an admin tool, etc.
  return Response.json({ users });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.lean()&lt;/code&gt; returns plain JavaScript objects, not real Mongoose documents. That &lt;code&gt;toJSON&lt;/code&gt; transform you configured on the schema is a method that exists on Mongoose document instances specifically, it never runs on a plain object. &lt;code&gt;Response.json({ users })&lt;/code&gt; calls &lt;code&gt;JSON.stringify&lt;/code&gt; under the hood, which on a lean plain object just serializes every property directly, no transform, no stripping, nothing. If &lt;code&gt;password&lt;/code&gt; was selected anywhere in that query chain, it goes straight into the API response, completely bypassing the safety net you specifically built to prevent exactly that.&lt;/p&gt;
&lt;h3 id=&quot;why-this-is-genuinely-easy-to-introduce&quot;&gt;Why This Is Genuinely Easy to Introduce&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;select('+password')&lt;/code&gt; and &lt;code&gt;.lean()&lt;/code&gt; combination often gets added at different times, for different reasons, by different people, or by the same developer months apart with the original toJSON safety net long forgotten. Someone adds &lt;code&gt;.lean()&lt;/code&gt; to a query for a legitimate performance reason, weeks or months after the &lt;code&gt;select('+password')&lt;/code&gt; was added for some specific internal need, comparing a password during a migration script, an admin debugging tool, and neither change alone looks dangerous in isolation. The combination is what's dangerous, and nothing about reviewing either change independently reveals that.&lt;/p&gt;
&lt;h3 id=&quot;the-actual-fix-never-rely-on-tojson-as-your-only-safety-net&quot;&gt;The Actual Fix: Never Rely on toJSON as Your Only Safety Net&lt;/h3&gt;
&lt;p&gt;The real lesson here isn't &quot;don't use &lt;code&gt;.lean()&lt;/code&gt;&quot;, it's genuinely great for performance and you should keep using it. The lesson is that &lt;code&gt;toJSON&lt;/code&gt; transforms are not a reliable safety net for anything you might combine with &lt;code&gt;.lean()&lt;/code&gt;, so the actual stripping needs to happen explicitly, regardless of whether the query happens to be lean or not.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// lib/serializers/user.ts
export function toSafeUser(user: any) {
  const { password, __v, ...safe } = user;
  return safe;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// app/api/users/route.ts
import { toSafeUser } from '@/lib/serializers/user';

export async function GET() {
  const users = await User.find().lean(); // no need to select password here at all
  return Response.json({ users: users.map(toSafeUser) });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An explicit serializer function works identically whether the input came from a lean query or a real document, since it's just plain object destructuring, not relying on a Mongoose-specific lifecycle hook that only fires under certain conditions.&lt;/p&gt;
&lt;h3 id=&quot;the-broader-rule-this-points-to&quot;&gt;The Broader Rule This Points To&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Never assume a security-relevant transform tied to a document lifecycle method still applies once you've introduced &lt;code&gt;.lean()&lt;/code&gt;, &lt;code&gt;.toObject()&lt;/code&gt;, or any pattern that returns a plain object instead of a real document instance.&lt;/strong&gt; &lt;code&gt;toJSON&lt;/code&gt;, &lt;code&gt;toObject&lt;/code&gt; transforms, and virtuals are all Mongoose document features, tied specifically to the document class, not properties of the raw data itself. The moment you're working with a plain object, none of that schema-level behavior comes along automatically, and any security assumption riding on it needs to be re-verified explicitly, not assumed to still hold.&lt;/p&gt;
&lt;h3 id=&quot;check-your-own-api-routes&quot;&gt;Check Your Own API Routes&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grep -rn &quot;lean()&quot; --include=&quot;*.ts&quot; app/api/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For each match, check two things, does the underlying query ever select a genuinely sensitive field, and does the response actually get filtered explicitly, not just relying on a schema-level transform that a lean query never triggers. If a route selects a sensitive field for some legitimate internal reason and also calls &lt;code&gt;.lean()&lt;/code&gt;, that combination is worth verifying directly against the actual JSON response, not just the code.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you've got &lt;code&gt;.lean()&lt;/code&gt; anywhere near a query that ever selects a genuinely sensitive field, go check the actual raw response, not just the code, this specific combination is exactly the kind of thing that looks completely fine on read-through and only reveals itself in the real serialized output. Drop what you find in the comments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26819/that-lean-query-optimization-might-be-leaking-password-hashes-in-your-api-response</guid>
<pubDate>Fri, 11 Sep 2026 08:29:25 +0000</pubDate>
</item>
<item>
<title>🚀 Building a Production Facebook Messenger → AI Pipeline with MyZubster &amp; Zorgax</title>
<link>https://coderlegion.com/26323/building-a-production-facebook-messenger-ai-pipeline-with-myzubster-zorgax</link>
<description>&lt;p&gt;🚀 Building a Production Facebook Messenger → AI Pipeline with MyZubster &amp;amp; Zorgax&lt;/p&gt;
&lt;p&gt;We just shipped a new integration in the MyZubster open-source ecosystem: Zorgax, our AI copilot, can now receive and answer messages directly through Facebook Messenger.&lt;/p&gt;
&lt;p&gt;The production flow is:&lt;/p&gt;
&lt;p&gt;Messenger → Meta Webhook → MyZubster API → Zorgax → Meta Send API → Messenger&lt;/p&gt;
&lt;p&gt;Getting the webhook to return 200 was only part of the job.&lt;/p&gt;
&lt;p&gt;One interesting production issue appeared after the webhook was already receiving Messenger events correctly: Meta received its 200 OK, but the asynchronous Zorgax processing that followed could be interrupted in the serverless environment.&lt;/p&gt;
&lt;p&gt;The solution was to keep Meta's acknowledgement fast while explicitly keeping the background work alive with Vercel's waitUntil().&lt;/p&gt;
&lt;p&gt;We also implemented and verified:&lt;/p&gt;
&lt;p&gt;Meta webhook challenge verification&lt;br&gt;
X-Hub-Signature-256 request validation&lt;br&gt;
secure environment-based App Secret and Page Access Token handling&lt;br&gt;
Messenger event processing&lt;br&gt;
MongoDB connectivity&lt;br&gt;
Zorgax AI processing&lt;br&gt;
Meta Send API responses&lt;br&gt;
multilingual conversations&lt;br&gt;
production deployment on Vercel&lt;/p&gt;
&lt;p&gt;The final production trace now looks like:&lt;/p&gt;
&lt;p&gt;POST /api/meta/messenger/webhook → 200&lt;br&gt;
Connected to MongoDB&lt;br&gt;
[meta-messenger] {&quot;event&quot;:&quot;message_handled&quot;,&quot;mode&quot;:&quot;zorgax&quot;}&lt;/p&gt;
&lt;p&gt;And, most importantly, the response actually makes the full trip back to the Messenger conversation. 🎯&lt;/p&gt;
&lt;p&gt;The code is open source&lt;/p&gt;
&lt;p&gt;The serverless/background-processing fix was shipped in:&lt;/p&gt;
&lt;p&gt;PR #1007 — fix(messenger): keep Zorgax reply alive after webhook ack&lt;/p&gt;
&lt;p&gt;Read PR #1007 on GitHub&lt;/p&gt;
&lt;p&gt;Explore the MyZubster repository&lt;/p&gt;
&lt;p&gt;Visit MyZubster&lt;/p&gt;
&lt;p&gt;This was a good reminder that “the webhook returned 200” doesn't necessarily mean the workflow completed—especially when asynchronous AI work happens after the HTTP response in a serverless environment.&lt;/p&gt;
&lt;p&gt;Now Zorgax has another gateway into the MyZubster ecosystem, and the implementation is available for other developers to inspect, improve, and build on.&lt;/p&gt;
&lt;p&gt;Open source. Production tested. Community built.&lt;/p&gt;
&lt;h2 id=&quot;opensource-nodejs-ai-facebookmessenger-metadevelopers-vercel-webhooks-apis-mongodb-buildinpublic-myzubster-zorgax&quot;&gt;OpenSource #NodeJS #AI #FacebookMessenger #MetaDevelopers #Vercel #Webhooks #APIs #MongoDB #BuildInPublic #MyZubster #Zorgax&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26323/building-a-production-facebook-messenger-ai-pipeline-with-myzubster-zorgax</guid>
<pubDate>Sun, 06 Sep 2026 14:45:08 +0000</pubDate>
</item>
<item>
<title>Next.js Automatically Dedupes fetch() Calls. Your MongoDB Queries Don't Get That for Free.</title>
<link>https://coderlegion.com/26198/next-js-automatically-dedupes-fetch-calls-your-mongodb-queries-dont-get-that-for-free</link>
<description>&lt;p&gt;Here's something that trips up almost everyone moving from a &lt;code&gt;fetch&lt;/code&gt;-based API to a direct MongoDB connection in Next.js, and it's costing real database load on projects that don't even realize it's happening.&lt;/p&gt;
&lt;p&gt;Next.js automatically deduplicates &lt;code&gt;fetch()&lt;/code&gt; calls with identical parameters within a single render pass. If your layout, your page, and three nested components all call &lt;code&gt;fetch('/api/user')&lt;/code&gt;, Next.js is smart enough to only actually make that request once and reuse the result everywhere else it's called. This is called request memoization, and it's one of the quieter, genuinely useful things the App Router does for you automatically.&lt;/p&gt;
&lt;p&gt;Direct database queries get none of this. Nothing.&lt;/p&gt;
&lt;h3 id=&quot;what-this-actually-looks-like&quot;&gt;What This Actually Looks Like&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// lib/queries/user.ts
import { connectDB } from '@/lib/db';
import User from '@/models/User';

export async function getCurrentUser(userId: string) {
  await connectDB();
  return User.findById(userId).lean();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/dashboard/layout.tsx
export default async function DashboardLayout({ children }) {
  const user = await getCurrentUser(userId); // query #1
  return &amp;lt;div&amp;gt;&amp;lt;Sidebar user={user} /&amp;gt;{children}&amp;lt;/div&amp;gt;;
}

// app/dashboard/page.tsx
export default async function DashboardPage() {
  const user = await getCurrentUser(userId); // query #2, same user, same request
  return &amp;lt;Header user={user} /&amp;gt;;
}

// components/UserMenu.tsx (Server Component, rendered inside the page)
export default async function UserMenu() {
  const user = await getCurrentUser(userId); // query #3
  return &amp;lt;Menu user={user} /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three separate calls to &lt;code&gt;getCurrentUser&lt;/code&gt;, same user ID, same single incoming request from the browser. If this were &lt;code&gt;fetch('/api/user')&lt;/code&gt; instead of a direct Mongoose query, Next.js would automatically collapse these into one actual network call. Since it's a direct database query, none of that automatic deduplication applies. This is three real round trips to MongoDB, for data that was identical all three times, on every single page load.&lt;/p&gt;
&lt;h3 id=&quot;why-this-doesn-t-show-up-in-development&quot;&gt;Why This Doesn't Show Up in Development&lt;/h3&gt;
&lt;p&gt;On a local database with almost no latency and no real load, three redundant queries feel instant, indistinguishable from one. This is exactly why it's so easy to ship without noticing. The cost only becomes visible under real production conditions, real network latency to your database, real concurrent traffic, where those extra round trips add up into measurably slower page loads and meaningfully higher database load than the page actually needed.&lt;/p&gt;
&lt;h3 id=&quot;the-actual-fix-react-s-cache-function&quot;&gt;The Actual Fix: React's cache() Function&lt;/h3&gt;
&lt;p&gt;React ships a &lt;code&gt;cache()&lt;/code&gt; function specifically for this. It memoizes a function's result within a single render pass, exactly the same behavior &lt;code&gt;fetch&lt;/code&gt; gets automatically, just applied manually to whatever function you wrap with it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// lib/queries/user.ts
import { cache } from 'react';
import { connectDB } from '@/lib/db';
import User from '@/models/User';

export const getCurrentUser = cache(async (userId: string) =&amp;gt; {
  await connectDB();
  return User.findById(userId).lean();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's the entire fix. Every call to &lt;code&gt;getCurrentUser&lt;/code&gt; with the same &lt;code&gt;userId&lt;/code&gt;, anywhere in that same render pass, layout, page, nested component, now returns the same cached promise instead of triggering a new query. Three calls become one actual database hit.&lt;/p&gt;
&lt;h3 id=&quot;the-part-people-get-wrong-about-cache-&quot;&gt;The Part People Get Wrong About cache()&lt;/h3&gt;
&lt;p&gt;This is request-scoped, not persistent across requests. It deduplicates calls within one single render of one single incoming request, then resets completely for the next request. It is not the same thing as &lt;code&gt;unstable_cache&lt;/code&gt;, which persists data across multiple requests and needs explicit revalidation.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// cache() - deduplicates within ONE request, resets every time
export const getCurrentUser = cache(async (userId: string) =&amp;gt; { ... });

// unstable_cache - persists ACROSS requests, needs revalidateTag to update
export const getPosts = unstable_cache(async () =&amp;gt; { ... }, ['posts'], {
  revalidate: 3600,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mixing these up causes two different problems. Using &lt;code&gt;cache()&lt;/code&gt; where you needed &lt;code&gt;unstable_cache&lt;/code&gt; means you're still hitting the database on every single request, just once instead of three times. Using &lt;code&gt;unstable_cache&lt;/code&gt; where you needed &lt;code&gt;cache()&lt;/code&gt; means you're persisting data across requests when you actually just wanted to avoid redundant calls within one page load, which can serve stale data longer than intended.&lt;/p&gt;
&lt;h3 id=&quot;where-i-d-actually-add-this&quot;&gt;Where I'd Actually Add This&lt;/h3&gt;
&lt;p&gt;Not everywhere, that would be over-engineering a solution for calls that only ever happen once per request anyway. The pattern worth watching for specifically: any query function, &lt;code&gt;getCurrentUser&lt;/code&gt;, &lt;code&gt;getTenant&lt;/code&gt;, &lt;code&gt;getSession&lt;/code&gt;-adjacent lookups, that reasonably gets called from multiple places in the same component tree, a layout, a page, and a few nested components all independently needing &quot;who is the current user.&quot;&lt;/p&gt;
&lt;h3 id=&quot;check-your-own-project&quot;&gt;Check Your Own Project&lt;/h3&gt;
&lt;p&gt;Grep your query functions for ones called from more than one component in the same tree, and check whether they're wrapped in &lt;code&gt;cache()&lt;/code&gt;. If they're not, and they're not already going through &lt;code&gt;unstable_cache&lt;/code&gt; or an equivalent persistent layer, you likely have the exact redundant-query pattern above running silently in production right now.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Curious how many people actually knew about this before reading this post versus assumed Next.js handled it automatically the same way it does for fetch. Drop your honest answer in the comments, genuinely curious how well-known this actually is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26198/next-js-automatically-dedupes-fetch-calls-your-mongodb-queries-dont-get-that-for-free</guid>
<pubDate>Fri, 04 Sep 2026 10:14:16 +0000</pubDate>
</item>
<item>
<title>Building MyZubster in Public: Marketplace, ZORGAX, LIFE Pilot, and a Verified Metaverse</title>
<link>https://coderlegion.com/25882/building-myzubster-in-public-marketplace-zorgax-life-pilot-and-a-verified-metaverse</link>
<description>&lt;p&gt;MyZubster has recently completed one of its most important development phases so far.&lt;/p&gt;
&lt;p&gt;We worked across the entire stack: production infrastructure, authentication, Marketplace visibility, economic APIs, digital entrepreneurship workflows, security automation, and public Metaverse identities.&lt;/p&gt;
&lt;p&gt;This is a technical overview of what we built, what broke along the way, and what is now running in production.&lt;/p&gt;
&lt;h3 id=&quot;the-myzubster-ecosystem&quot;&gt;The MyZubster ecosystem&lt;/h3&gt;
&lt;p&gt;MyZubster is an open-source ecosystem that combines several connected services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A community Marketplace&lt;/li&gt;
&lt;li&gt;GitHub-based user identities&lt;/li&gt;
&lt;li&gt;ZORGAX, an assistant and economic experimentation layer&lt;/li&gt;
&lt;li&gt;A guided digital entrepreneurship program called LIFE Pilot&lt;/li&gt;
&lt;li&gt;A persistent Metaverse called Neon Plaza&lt;/li&gt;
&lt;li&gt;Public contributor and project records&lt;/li&gt;
&lt;li&gt;Automated testing, security, and deployment workflows&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The project is available on GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MyZubster-Ecosystem/myzubster&quot;&gt;github.com/MyZubster-Ecosystem/myzubster&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The production platform is available at:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/&quot;&gt;www.myzubster.com&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;fixing-the-production-foundation&quot;&gt;Fixing the production foundation&lt;/h3&gt;
&lt;p&gt;Before adding new functionality, we had to solve several production-level problems.&lt;/p&gt;
&lt;p&gt;The application was deployed, but some important features were not working reliably.&lt;/p&gt;
&lt;p&gt;The main issues included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MongoDB operations timing out&lt;/li&gt;
&lt;li&gt;GitHub OAuth failing after authorization&lt;/li&gt;
&lt;li&gt;Marketplace data not loading in production&lt;/li&gt;
&lt;li&gt;Backend requests being captured by frontend routes&lt;/li&gt;
&lt;li&gt;Node.js runtime incompatibility with the MongoDB driver&lt;/li&gt;
&lt;li&gt;Dependency lockfile differences during clean Vercel builds&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;mongodb-and-github-oauth&quot;&gt;MongoDB and GitHub OAuth&lt;/h4&gt;
&lt;p&gt;GitHub authentication could start correctly, but the login process failed when the application attempted to query the users collection.&lt;/p&gt;
&lt;p&gt;The operation remained buffered until it timed out.&lt;/p&gt;
&lt;p&gt;We corrected the production MongoDB configuration and verified that the application could connect to the database before executing authentication queries.&lt;/p&gt;
&lt;p&gt;We also aligned the production runtime with Node.js 20, which provides a stable environment for the current MongoDB driver and application dependencies.&lt;/p&gt;
&lt;p&gt;After the fix:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub OAuth completes successfully&lt;/li&gt;
&lt;li&gt;User records can be retrieved or created&lt;/li&gt;
&lt;li&gt;Marketplace listings can access persistent data&lt;/li&gt;
&lt;li&gt;API routes no longer fail because of buffered database operations&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;making-the-marketplace-visible&quot;&gt;Making the Marketplace visible&lt;/h3&gt;
&lt;p&gt;The Marketplace was already present in the repository, but it was not correctly exposed through the production application.&lt;/p&gt;
&lt;p&gt;We restored its route, connected it to the live API, and verified that the listing endpoint returned a successful response from production.&lt;/p&gt;
&lt;p&gt;The Marketplace is now accessible here:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.myzubster.com/marketplace&quot;&gt;Open the MyZubster Marketplace&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This provides the base for future services, contributor offers, digital products, and community exchanges.&lt;/p&gt;
&lt;h3 id=&quot;zorgax-pricing-and-access-model&quot;&gt;ZORGAX pricing and access model&lt;/h3&gt;
&lt;p&gt;ZORGAX now exposes a public pricing API with three plans:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zorgax Free&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zorgax Pro&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zorgax Developer&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The current Pro reference price is €9.90 per month.&lt;/p&gt;
&lt;p&gt;The Developer plan is intended for users who need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API access&lt;/li&gt;
&lt;li&gt;Automation&lt;/li&gt;
&lt;li&gt;Higher usage limits&lt;/li&gt;
&lt;li&gt;Advanced workspace functionality&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The payment architecture is explicitly non-custodial.&lt;/p&gt;
&lt;p&gt;MyZubster does not request or accept users’ private keys, and it does not automatically sign blockchain transactions.&lt;/p&gt;
&lt;p&gt;External settlement must be independently verified before paid access is activated.&lt;/p&gt;
&lt;p&gt;This separation is critical:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;A payment request is not proof of payment, and an unconfirmed transaction is not confirmed revenue.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;building-the-zorgax-economic-layer&quot;&gt;Building the ZORGAX economic layer&lt;/h3&gt;
&lt;p&gt;ZORGAX has evolved from a basic assistant into a more structured economic experimentation system.&lt;/p&gt;
&lt;p&gt;We added services and APIs for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Confirmed economic inflows&lt;/li&gt;
&lt;li&gt;Capital-allocation proposals&lt;/li&gt;
&lt;li&gt;Advisory capital recommendations&lt;/li&gt;
&lt;li&gt;Capital decision tracking&lt;/li&gt;
&lt;li&gt;Outcome recording&lt;/li&gt;
&lt;li&gt;Decision memory&lt;/li&gt;
&lt;li&gt;Bounded learning&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;confirmed-inflows-only&quot;&gt;Confirmed inflows only&lt;/h4&gt;
&lt;p&gt;The economic layer does not treat every payment intent as available capital.&lt;/p&gt;
&lt;p&gt;Only independently confirmed inflows are included in the capital model.&lt;/p&gt;
&lt;p&gt;This prevents pending, rejected, or unverified transactions from influencing recommendations.&lt;/p&gt;
&lt;h4 id=&quot;advisory-capital-allocation&quot;&gt;Advisory capital allocation&lt;/h4&gt;
&lt;p&gt;ZORGAX can generate proposals about how available resources could be allocated.&lt;/p&gt;
&lt;p&gt;These recommendations are advisory. They do not automatically transfer money or execute financial operations.&lt;/p&gt;
&lt;p&gt;A server-side policy constrains how the recommendations are generated.&lt;/p&gt;
&lt;h4 id=&quot;decision-and-outcome-memory&quot;&gt;Decision and outcome memory&lt;/h4&gt;
&lt;p&gt;Capital decisions can be recorded together with their eventual outcomes.&lt;/p&gt;
&lt;p&gt;The system can use this history to improve later recommendations through bounded learning.&lt;/p&gt;
&lt;p&gt;Bounded learning means the system can learn from evidence while remaining inside explicit operational limits.&lt;/p&gt;
&lt;p&gt;It cannot silently expand its own authority.&lt;/p&gt;
&lt;h3 id=&quot;from-idea-to-digital-product&quot;&gt;From idea to digital product&lt;/h3&gt;
&lt;p&gt;We also built a structured digital entrepreneurship workflow.&lt;/p&gt;
&lt;p&gt;The workflow can guide a project through the following stages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Project creation&lt;/li&gt;
&lt;li&gt;Idea intake&lt;/li&gt;
&lt;li&gt;Idea validation&lt;/li&gt;
&lt;li&gt;Product blueprint generation&lt;/li&gt;
&lt;li&gt;Offer creation&lt;/li&gt;
&lt;li&gt;Launch preparation&lt;/li&gt;
&lt;li&gt;Metrics collection&lt;/li&gt;
&lt;li&gt;Evidence-based learning&lt;/li&gt;
&lt;li&gt;Product iteration&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each stage has dedicated backend services, API routes, data models, and automated tests.&lt;/p&gt;
&lt;h3 id=&quot;digital-idea-validation&quot;&gt;Digital idea validation&lt;/h3&gt;
&lt;p&gt;A user can submit a digital product idea and receive a structured validation result.&lt;/p&gt;
&lt;p&gt;The validation engine evaluates the idea and creates a persistent snapshot.&lt;/p&gt;
&lt;p&gt;This makes it possible to compare different iterations instead of overwriting previous reasoning.&lt;/p&gt;
&lt;p&gt;The workflow connects validation results to the project so that later stages can use real evidence.&lt;/p&gt;
&lt;h3 id=&quot;product-blueprint-engine&quot;&gt;Product blueprint engine&lt;/h3&gt;
&lt;p&gt;A validated idea can be converted into a digital product blueprint.&lt;/p&gt;
&lt;p&gt;The blueprint provides a structured foundation for the product, including its direction, audience, and execution model.&lt;/p&gt;
&lt;p&gt;The latest blueprint is persisted with the project and exposed through a dedicated API.&lt;/p&gt;
&lt;p&gt;This creates a bridge between an idea and an actionable product plan.&lt;/p&gt;
&lt;h3 id=&quot;launch-and-offer-engine&quot;&gt;Launch and offer engine&lt;/h3&gt;
&lt;p&gt;The launch-and-offer engine converts the product blueprint into a controlled offer package.&lt;/p&gt;
&lt;p&gt;Instead of stopping at strategy, the workflow prepares something that can be presented to real users and tested.&lt;/p&gt;
&lt;p&gt;The resulting package can then be connected to product metrics and learning events.&lt;/p&gt;
&lt;h3 id=&quot;metrics-and-learning&quot;&gt;Metrics and learning&lt;/h3&gt;
&lt;p&gt;A digital product needs more than a launch plan. It needs measurable feedback.&lt;/p&gt;
&lt;p&gt;We added:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Product metric event models&lt;/li&gt;
&lt;li&gt;Metric collection services&lt;/li&gt;
&lt;li&gt;Public API routes&lt;/li&gt;
&lt;li&gt;Learning summaries&lt;/li&gt;
&lt;li&gt;Tests for metric aggregation and learning behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The objective is to create a loop:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Idea
  ↓
Validation
  ↓
Blueprint
  ↓
Offer
  ↓
Launch
  ↓
Metrics
  ↓
Learning
  ↓
Next iteration
The public response does not expose:
- Email addresses
- Password data
- Password hashes
- Internal MongoDB identifiers
- Private GitHub identifiers
- Private verification metadata
- Authentication credentials
The database query also uses an explicit field projection so that unnecessary data is not loaded before the response is created.
Security automation
Security work was added directly to the development workflow.
The repository now includes:
- Automated security checks
- Continuous evidence gates
- Pull-request security verification
- Post-remediation security records
- Public security documentation
- Regression protection for critical routes
The latest Marketplace remediation recorded zero remaining HIGH-severity findings.
The goal is to make security evidence reproducible.
Instead of saying that a feature is secure, the project records what was tested and protects the result with automated checks.
Test coverage
Tests were added across the new services and integration boundaries.
The coverage includes:
- Marketplace behavior and security
- Metaverse public statistics
- Verified explorer responses
- ZORGAX pricing and product APIs
- Capital recommendations
- Capital decision lifecycle
- Confirmed inflow aggregation
- Digital idea validation
- Product blueprint generation
- Launch offer generation
- Metrics and learning
- Pilot enrollment
- Pilot onboarding
- First-project creation
- Idea ranking
- LIFE Pilot API routing
- Vercel route precedence
The final H4x0r release passed backend route tests and frontend syntax validation before deployment.
Production verification
The latest changes were committed to the main branch and deployed automatically through Vercel.
After deployment, we verified:
- The deployment reached the Ready state
- The production API returned HTTP 200
- H4x0r appeared in featuredCharacters
- Only approved public fields were present
- No sensitive fields were exposed
- The frontend displayed the verified explorer card
- The public GitHub link worked correctly
The release commit is available here:
feat(metaverse): publish verified explorer profiles
Engineering lessons
This development phase produced several useful lessons.
1. Production configuration is part of the codebase
A correct application can still fail because of runtime versions, environment variables, dependency metadata, or proxy routing.
2. Public APIs need explicit boundaries
Never expose complete database documents by default.
A public API should intentionally select and transform every field it returns.
3. Payment detection and payment confirmation are different states
Paid access should not be activated from a payment intent alone.
Settlement must be independently verified.
4. Automation needs authority limits
An intelligent system can generate recommendations and learn from outcomes without receiving permission to execute financial actions.
5. Integration tests protect the real product
Many important failures happen between components rather than inside individual functions.
Authentication, databases, frontend routing, deployment proxies, and settlement verification all require integration-level protection.
What comes next?
The next stages of MyZubster development include:
- Completing verified paid-access activation
- Expanding Marketplace services
- Adding more verified explorers to Neon Plaza
- Improving the LIFE Pilot user experience
- Strengthening production monitoring
- Publishing clearer governance and contributor records
- Exploring resilient and decentralized service distribution
- Continuing security and privacy improvements
MyZubster is still evolving, but it now has a much stronger foundation.
The ecosystem has persistent identities, working authentication, public services, economic experiments, guided project workflows, privacy-safe APIs, automated tests, and a verified production deployment.
If you want to inspect the code or contribute, visit:
MyZubster Ecosystem on GitHub
You can explore the live platform here:
www.myzubster.com
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25882/building-myzubster-in-public-marketplace-zorgax-life-pilot-and-a-verified-metaverse</guid>
<pubDate>Mon, 31 Aug 2026 00:33:06 +0000</pubDate>
</item>
<item>
<title>How to Build a SaaS with Next.js and MongoDB in 2026 (Complete Guide)</title>
<link>https://coderlegion.com/25611/how-to-build-a-saas-with-next-js-and-mongodb-in-2026-complete-guide</link>
<description>&lt;p&gt;Building a SaaS product from scratch involves the same handful of pieces every time. Authentication, a database that actually scales, subscription billing, and a dashboard people can use without a manual. None of these are individually hard. Getting all of them working together, correctly, is where most side projects stall out.&lt;/p&gt;
&lt;p&gt;This is the full path, from an empty folder to something you could actually charge people for, using Next.js 15 and MongoDB.&lt;/p&gt;
&lt;h3 id=&quot;step-1-set-up-the-foundation&quot;&gt;Step 1: Set Up the Foundation&lt;/h3&gt;
&lt;p&gt;Start with the App Router, TypeScript, and a folder structure that will not need restructuring once the project grows. Route groups separate your public marketing pages, authenticated dashboard, and API routes cleanly, without affecting the actual URLs.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/
├── app/
│   ├── (marketing)/     ← public landing pages
│   ├── (auth)/          ← login, signup
│   ├── (dashboard)/     ← authenticated app
│   └── api/webhooks/    ← Stripe, etc.
├── lib/
├── models/
└── actions/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Getting this right early avoids the painful restructuring that happens when a flat &lt;code&gt;app/&lt;/code&gt; folder grows past a dozen pages with no separation between public and authenticated routes.&lt;/p&gt;
&lt;h3 id=&quot;step-2-connect-mongodb-the-right-way&quot;&gt;Step 2: Connect MongoDB the Right Way&lt;/h3&gt;
&lt;p&gt;A cached connection pattern is non-negotiable for anything deployed on Vercel or another serverless platform. Without it, hot reloads in development and cold starts in production both create new connections faster than MongoDB wants to accept them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// lib/db.ts
import mongoose from 'mongoose';

let cached = (global as any).mongoose || { conn: null, promise: null };

export async function connectDB() {
  if (cached.conn) return cached.conn;
  if (!cached.promise) {
    cached.promise = mongoose.connect(process.env.MONGODB_URI as string);
  }
  cached.conn = await cached.promise;
  return cached.conn;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From here, every model, User, Subscription, and whatever your product's core data actually is, follows the same pattern, checking &lt;code&gt;mongoose.models.X&lt;/code&gt; before defining a new one so hot reload does not throw an overwrite error.&lt;/p&gt;
&lt;h3 id=&quot;step-3-authentication&quot;&gt;Step 3: Authentication&lt;/h3&gt;
&lt;p&gt;JWT stored in an httpOnly cookie keeps the token out of reach of client-side JavaScript, which closes off the most common token-theft path through XSS. Middleware checks the cookie before a protected route even starts rendering, and a second check inside the dashboard layout catches anything middleware might miss.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// middleware.ts
export function middleware(request: NextRequest) {
  const session = request.cookies.get('auth-token');
  if (!session &amp;amp;&amp;amp; request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Role-based access, separating &quot;not logged in&quot; from &quot;logged in but not allowed here&quot;, sits on top of this once you have more than one type of user, admin versus regular user, for example.&lt;/p&gt;
&lt;h3 id=&quot;step-4-subscription-billing-with-stripe&quot;&gt;Step 4: Subscription Billing with Stripe&lt;/h3&gt;
&lt;p&gt;This is the step people try to shortcut, and it always causes problems later. Do not update subscription status based on the success redirect after checkout. Browsers close, redirects fail to load, and your database never finds out what actually happened.&lt;/p&gt;
&lt;p&gt;Webhooks are the only reliable source of truth:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// app/api/webhooks/stripe/route.ts
case 'checkout.session.completed': {
  const session = event.data.object as Stripe.Checkout.Session;
  await User.findByIdAndUpdate(session.metadata?.userId, {
    subscriptionStatus: 'active',
  });
  break;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rest of your app reads &lt;code&gt;subscriptionStatus&lt;/code&gt; straight from the database, never calling Stripe directly just to check if a user has access. The webhook is what keeps that field accurate.&lt;/p&gt;
&lt;h3 id=&quot;step-5-the-dashboard-itself&quot;&gt;Step 5: The Dashboard Itself&lt;/h3&gt;
&lt;p&gt;Server Components handle almost all of the actual data fetching, no client-side loading spinners for data that is available the moment the page renders. Search, filters, and pagination live in the URL rather than component state, which keeps a filtered view shareable and refresh-safe.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/(dashboard)/customers/page.tsx
export default async function CustomersPage({ searchParams }) {
  const { search, page } = await searchParams;
  const { customers, totalPages } = await getCustomers({ search, page });

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;CustomerFilters /&amp;gt;
      &amp;lt;CustomerList customers={customers} /&amp;gt;
      &amp;lt;Pagination currentPage={Number(page) || 1} totalPages={totalPages} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Client Components only wrap the pieces that are actually interactive, a filter input, a modal, a button with local state, keeping the JavaScript shipped to the browser as small as it can reasonably be.&lt;/p&gt;
&lt;h3 id=&quot;step-6-forms-validation-and-error-handling&quot;&gt;Step 6: Forms, Validation, and Error Handling&lt;/h3&gt;
&lt;p&gt;One Zod schema per form, shared between client-side validation (instant feedback as someone types) and the Server Action itself (the real check, since client validation can always be bypassed). Route-level &lt;code&gt;error.tsx&lt;/code&gt; files catch anything unexpected without taking down the rest of the app, and &lt;code&gt;notFound()&lt;/code&gt; handles missing records cleanly as an actual 404 rather than a crash.&lt;/p&gt;
&lt;h3 id=&quot;step-7-email-and-notifications&quot;&gt;Step 7: Email and Notifications&lt;/h3&gt;
&lt;p&gt;Verification emails, password resets, and payment receipts are expected in any real SaaS. Triggering the receipt email from inside the Stripe webhook, not from the client-side redirect, keeps it reliable for the same reason the subscription status update needs to happen there. Writing templates as actual React components, rather than raw HTML strings, makes this part of the stack far less painful to maintain.&lt;/p&gt;
&lt;h3 id=&quot;step-8-real-time-features-if-you-need-them&quot;&gt;Step 8: Real-Time Features, If You Need Them&lt;/h3&gt;
&lt;p&gt;Not every SaaS needs this, but anything with a live status, a queue, a dashboard that multiple people watch at once, benefits from pushing updates instead of polling. Trigger events from the server after a database write, and treat the database as the source of truth, with real-time events as a fast notification layer on top rather than a replacement for it.&lt;/p&gt;
&lt;h3 id=&quot;step-9-rate-limiting-and-security&quot;&gt;Step 9: Rate Limiting and Security&lt;/h3&gt;
&lt;p&gt;Public endpoints, a contact form, a signup route, need rate limiting before they see real traffic, not after something abuses them. A sliding window limiter keyed by IP for unauthenticated routes, or by user ID for authenticated ones, catches both accidental retry loops and deliberate abuse. Every route still needs its own input validation on top of this, since rate limiting controls how often, not what gets sent.&lt;/p&gt;
&lt;h3 id=&quot;step-10-seo-for-the-marketing-pages&quot;&gt;Step 10: SEO for the Marketing Pages&lt;/h3&gt;
&lt;p&gt;The dashboard itself does not need to rank in search results, but the marketing pages around it do. Unique metadata per page through &lt;code&gt;generateMetadata&lt;/code&gt;, a generated sitemap that updates automatically as content is added, and canonical URLs to avoid a www versus non-www split are the baseline. From there, actual content, a blog, comparison pages, documentation, is what builds the organic traffic a SaaS needs to reduce dependence on paid acquisition.&lt;/p&gt;
&lt;h3 id=&quot;what-this-actually-takes-to-build-from-scratch&quot;&gt;What This Actually Takes to Build From Scratch&lt;/h3&gt;
&lt;p&gt;Realistically, a solo developer building all of this correctly, connection caching, auth, billing, a working dashboard, emails, basic security, is looking at real weeks of work before the first paying customer could even sign up, longer if any of these pieces are unfamiliar.&lt;/p&gt;
&lt;p&gt;This is exactly the gap a well-built template closes. Not because the concepts are hard to learn, everything above is a pattern, not a secret, but because assembling all of them correctly, with the pieces actually wired together instead of built in isolation, is where the time actually goes.&lt;/p&gt;
&lt;h3 id=&quot;frequently-asked-questions&quot;&gt;Frequently Asked Questions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Is MongoDB a good choice for a SaaS product, or should I use a SQL database?&lt;/strong&gt;&lt;br&gt;
MongoDB works well for SaaS products with flexible or evolving data shapes, since schema changes do not require a migration in the same way a relational database does. For data with strict relational integrity requirements, financial ledgers, complex multi-table joins, a SQL database like Postgres is often a better fit. Many SaaS products end up using both, MongoDB for flexible application data, a relational database for anything requiring strict consistency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How long does it take to build a SaaS MVP with Next.js?&lt;/strong&gt;&lt;br&gt;
For an experienced developer working solo, a focused MVP covering auth, one core feature, and basic billing typically takes several weeks of consistent work. Scope is the biggest variable, an MVP that tries to include every feature from the start almost always takes longer than one built around a single, well-defined core workflow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do I need a separate backend, or can Next.js handle the whole SaaS?&lt;/strong&gt;&lt;br&gt;
For most SaaS products, Next.js alone is sufficient. Server Actions and API routes handle backend logic, and Server Components handle data fetching, without needing a separate Express or Node server running alongside it. A separate backend becomes worth considering only for very specific needs, like a long-running background process that does not fit a serverless request-response model.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you want to see this exact architecture already built, rather than assembling it piece by piece, I sell Next.js and MongoDB SaaS templates with auth, billing, and a working dashboard already wired together.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Browse templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Building something specific and want a hand with it? &lt;strong&gt;Get in touch:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.com/contact&quot;&gt;https://pixelanas.com/contact&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25611/how-to-build-a-saas-with-next-js-and-mongodb-in-2026-complete-guide</guid>
<pubDate>Fri, 28 Aug 2026 09:41:09 +0000</pubDate>
</item>
<item>
<title>How I Used PostgreSQL Events and MongoDB Event Logs in Two Different Real-World Systems</title>
<link>https://coderlegion.com/21940/how-i-used-postgresql-events-and-mongodb-event-logs-in-two-different-real-world-systems</link>
<description>&lt;p&gt;I built two systems that both needed event tracking.&lt;br&gt;
I used PostgreSQL in one and MongoDB in the other.&lt;br&gt;
Here's exactly why — and what I learned from doing both.&lt;br&gt;
Although the two systems were completely different in purpose, they shared one architectural requirement: each needed a reliable way to record everything that happened — not just the final state, but the story of how that state came to be.&lt;br&gt;
That sounds simple, but the nature of the events in each system was so different that using the same database for both would have caused real problems. This article explains how I approached event tracking in each system, why the solutions diverged, and what the experience taught me about choosing the right persistence model.&lt;/p&gt;
&lt;h3 id=&quot;postgresql-events-and-projections-in-the-delivery-backend&quot;&gt;PostgreSQL Events and Projections in the Delivery Backend&lt;/h3&gt;
&lt;p&gt;The delivery backend was built around structured workflows: vendors create orders, couriers pick them up, jobs move through well-defined states, and batches group multiple deliveries. Every action in this system has a clear relational meaning. An event like ORDER_CREATED or JOB_STARTED isn't just a log entry — it's part of the domain model.&lt;br&gt;
Because of that, PostgreSQL was the natural home for events. Each event needed to be tied to a specific entity, validated against constraints, indexed for fast lookup, and projected into timeline tables that the dashboard could query efficiently.&lt;br&gt;
The event log table looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;model EventLog {
  id          Int             @id @default(autoincrement())
  entityType  EventEntityType
  entityId    Int
  eventType   String
  payload     Json
  createdAt   DateTime        @default(now())

  @@index([entityType, entityId, createdAt])
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every event is immutable. Every event belongs to a relational entity. Every event carries structured metadata.&lt;br&gt;
Once an event is written, it is immediately transformed into a timeline entry:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;switch (event.entityType) {
  case EventEntityType.ORDER:
    await this.orderTimelineProjection.project(event);
    break;

  case EventEntityType.JOB:
    await this.jobTimelineProjection.project(event);
    break;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This projection layer is what makes the system feel fast. Instead of running complex joins to reconstruct history, the dashboard reads from dedicated timeline tables optimized for sequential queries. It also makes debugging easier: if something goes wrong, I can replay the event stream and see exactly how the system reached its current state.&lt;br&gt;
PostgreSQL excels in this environment because the events are structured, relational, and part of the domain's consistency model. They aren't just analytics — they're part of the truth.&lt;/p&gt;
&lt;h3 id=&quot;mongodb-event-logs-and-aggregation-in-waitless&quot;&gt;MongoDB Event Logs and Aggregation in Waitless&lt;/h3&gt;
&lt;p&gt;Waitless is a completely different kind of system. It's a real-time queue management platform where customers join via QR code, track their position live, and leave without creating an account. The system generates a lot of events — but these events are not relational. They're behavioral.&lt;br&gt;
A CUSTOMER_JOINED event might contain the customer's position.&lt;br&gt;
A NO_SHOW event might contain a timestamp.&lt;br&gt;
A CUSTOMER_LEFT event might contain a reason.&lt;br&gt;
A CALLED_NEXT event might contain which token was called.&lt;/p&gt;
&lt;p&gt;The shape of the metadata changes depending on the event type. Forcing this into a rigid SQL schema would either require dozens of nullable columns or a JSON column that PostgreSQL would struggle to index efficiently at scale.&lt;br&gt;
MongoDB is built for exactly this. Each event is a document with flexible metadata:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;@Schema({ timestamps: true })
export class QueueEvent {
  @Prop({ required: true })
  queueId!: string;

  @Prop({ required: true, enum: QueueEventType })
  eventType!: QueueEventType;

  @Prop()
  token?: string;

  @Prop({ type: Object })
  metadata?: Record&amp;lt;string, any&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This flexibility becomes powerful when combined with MongoDB's aggregation pipeline. Waitless computes analytics like total joined, total arrived, total no-shows, completion rate, and drop-off rate — all derived from event counts, not relational queries.&lt;br&gt;
A note on Redis: Waitless already had Redis in the stack backing BullMQ. But Redis is a key-value store optimized for job queues and caching — not for querying across documents or running aggregations. MongoDB was the right fit for this specific need.&lt;br&gt;
The separation between MongoDB and PostgreSQL is also crucial for performance. If analytics were computed in PostgreSQL, every dashboard refresh would compete with real-time queue operations for the same connection pool.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;If I had used PostgreSQL for Waitless events, every analytics query would hit the same connection pool as the queue state — meaning a slow dashboard query could delay a customer joining a queue.&lt;br&gt;
MongoDB avoids this entirely. It handles flexible event shapes, high write throughput, and analytics workloads without interfering with the transactional state stored in PostgreSQL.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;why-postgresql-for-one-and-mongodb-for-the-other-&quot;&gt;Why PostgreSQL for One and MongoDB for the Other?&lt;/h3&gt;
&lt;p&gt;The answer becomes clear once you see the nature of the data.&lt;br&gt;
In the delivery backend, events are part of the domain model. They describe state transitions, belong to relational entities, and require strong consistency. PostgreSQL is the right tool.&lt;br&gt;
In Waitless, events are part of analytics. They are flexible, metadata-heavy, and high-volume. They have no relational meaning — they're behavioral observations about what happened in a queue session. MongoDB is the right tool.&lt;br&gt;
Using PostgreSQL for both would have created unnecessary coupling and performance bottlenecks. Using MongoDB for both would have sacrificed relational guarantees where they mattered most.&lt;br&gt;
This is the essence of polyglot persistence: choose the database that matches the shape and purpose of the data, not the one you're most comfortable with.&lt;/p&gt;
&lt;h3 id=&quot;the-question-that-actually-matters&quot;&gt;The Question That Actually Matters&lt;/h3&gt;
&lt;p&gt;Building two event-driven systems taught me that &quot;SQL vs NoSQL&quot; is the wrong question entirely.&lt;br&gt;
The real question is: what role do your events play in the system?&lt;br&gt;
If events belong to the domain model — use a relational database.&lt;br&gt;
If events belong to analytics — use a document database.&lt;br&gt;
PostgreSQL gave me structured timelines and strong consistency. MongoDB gave me flexible event logs and fast aggregation. Both systems use events, but the events serve completely different masters.&lt;br&gt;
Understanding that difference before writing a single line of code is what separates a working system from a well-designed one.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21940/how-i-used-postgresql-events-and-mongodb-event-logs-in-two-different-real-world-systems</guid>
<pubDate>Sun, 05 Jul 2026 09:20:40 +0000</pubDate>
</item>
<item>
<title>MongoDB DR Drill Automation with Terraform, Python &amp; Jenkins — How We Made Restores Boring</title>
<link>https://coderlegion.com/21768/mongodb-dr-drill-automation-with-terraform-python-jenkins-how-we-made-restores-boring</link>
<description>&lt;h3 id=&quot;backups-don-t-save-you-restores-do-&quot;&gt;Backups Don't Save You. Restores Do.&lt;/h3&gt;
&lt;p&gt;We ran a MongoDB restore drill last quarter. It failed — not the restore itself, but the confidence. Nobody in the room was sure the data was actually intact. The service came back up, and we all just stared at each other.&lt;/p&gt;
&lt;p&gt;That was the problem. So we fixed it by automating everything.&lt;/p&gt;
&lt;p&gt;One Jenkins job now provisions infra, builds the replica set, restores from dumps, validates data integrity, and stores a full audit trail. Here's exactly how it works.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-goal&quot;&gt;The Goal&lt;/h3&gt;
&lt;p&gt;Remove every manual, error-prone step from the DR process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Identical restore flow across all environments&lt;/li&gt;
&lt;li&gt;Automated replica set setup — no manual &lt;code&gt;rs.initiate()&lt;/code&gt; typos&lt;/li&gt;
&lt;li&gt;Real validation that &lt;em&gt;proves&lt;/em&gt; data is intact, not just assumed&lt;/li&gt;
&lt;li&gt;Full audit trail for post-mortems and compliance reviews&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-pipeline-5-stages&quot;&gt;The Pipeline: 5 Stages&lt;/h3&gt;
&lt;h4 id=&quot;1-infrastructure-with-terraform&quot;&gt;1. Infrastructure with Terraform&lt;/h4&gt;
&lt;p&gt;Every drill starts with clean infra. Terraform provisions EC2s, networking, and persistent volumes from scratch — same starting point every time. No leftover state. No &quot;works on my machine&quot; surprises.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-hcl&quot;&gt;resource &quot;aws_instance&quot; &quot;mongo_node&quot; {
  count         = 3
  ami           = var.mongo_ami
  instance_type = &quot;t3.medium&quot;
  tags = {
    Name = &quot;mongo-dr-node-${count.index}&quot;
    Role = &quot;mongodb-replica&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-replica-set-creation-python-&quot;&gt;2. Replica Set Creation (Python)&lt;/h4&gt;
&lt;p&gt;Instead of manually running &lt;code&gt;rs.initiate()&lt;/code&gt; and &lt;code&gt;rs.add()&lt;/code&gt; and hoping the timing works, a Python script handles the entire setup — ordering, retries, and confirmation.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pymongo import MongoClient
import time

def init_replica_set(primary_host, secondary_hosts):
    client = MongoClient(f&quot;mongodb://{primary_host}:27017&quot;)
    config = {
        &quot;_id&quot;: &quot;rs0&quot;,
        &quot;members&quot;: [{&quot;_id&quot;: i, &quot;host&quot;: h}
                    for i, h in enumerate([primary_host] + secondary_hosts)]
    }
    client.admin.command(&quot;replSetInitiate&quot;, config)
    # Wait for PRIMARY election
    for _ in range(30):
        status = client.admin.command(&quot;replSetGetStatus&quot;)
        if any(m[&quot;stateStr&quot;] == &quot;PRIMARY&quot; for m in status[&quot;members&quot;]):
            return True
        time.sleep(2)
    raise Exception(&quot;Replica set did not elect a PRIMARY in time&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Automating this removes timing issues and misconfiguration. Every replica set comes up the same way.&lt;/p&gt;
&lt;h4 id=&quot;3-backup-amp-restore&quot;&gt;3. Backup &amp;amp; Restore&lt;/h4&gt;
&lt;p&gt;Backups are normalized into compressed archives. The restore unpacks a dump and applies it to the fresh nodes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Create dump
mongodump --host $SOURCE_HOST --db $DB_NAME \
  --out /backup/dump --gzip

# Restore to DR environment
mongorestore --host $DR_HOST --db $DB_NAME \
  /backup/dump/$DB_NAME --gzip --drop
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;4-validation-amp-comparison-the-part-most-teams-skip&quot;&gt;4. Validation &amp;amp; Comparison — The Part Most Teams Skip&lt;/h4&gt;
&lt;p&gt;This is the step that actually builds confidence. The validation script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Checks which collections exist (flags missing collections)&lt;/li&gt;
&lt;li&gt;Compares document counts collection by collection&lt;/li&gt;
&lt;li&gt;Compares indexes between source and restored DB&lt;/li&gt;
&lt;li&gt;Samples &lt;code&gt;_id&lt;/code&gt; values for obvious data mismatches&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def validate_restore(source_uri, dr_uri, db_name):
    src = MongoClient(source_uri)[db_name]
    dr  = MongoClient(dr_uri)[db_name]

    report = {&quot;status&quot;: &quot;pass&quot;, &quot;collections&quot;: {}}

    for col in src.list_collection_names():
        src_count = src[col].count_documents({})
        dr_count  = dr[col].count_documents({})
        src_idx   = sorted(src[col].index_information().keys())
        dr_idx    = sorted(dr[col].index_information().keys())

        match = (src_count == dr_count) and (src_idx == dr_idx)
        report[&quot;collections&quot;][col] = {
            &quot;count_match&quot;:  match,
            &quot;source_count&quot;: src_count,
            &quot;dr_count&quot;:     dr_count,
            &quot;index_match&quot;:  src_idx == dr_idx
        }
        if not match:
            report[&quot;status&quot;] = &quot;fail&quot;

    return report
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Exit code &lt;code&gt;0&lt;/code&gt; = counts and indexes match → Jenkins passes.&lt;br&gt;
Non-zero = mismatch → Jenkins fails the build immediately.&lt;/p&gt;
&lt;p&gt;No more guessing. No more staring at each other in the war room.&lt;/p&gt;
&lt;h4 id=&quot;5-jenkins-orchestration&quot;&gt;5. Jenkins Orchestration&lt;/h4&gt;
&lt;p&gt;Single Jenkins pipeline. Stages run sequentially, each one gated on the previous:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-groovy&quot;&gt;pipeline {
  agent any
  stages {
    stage('Provision Infra') {
      steps {
        sh 'terraform init &amp;amp;&amp;amp; terraform apply -auto-approve'
      }
    }
    stage('Setup Replica Set') {
      steps {
        sh 'python3 scripts/init_replica_set.py'
      }
    }
    stage('Restore MongoDB') {
      steps {
        sh 'bash scripts/restore.sh'
      }
    }
    stage('Validate Restore') {
      steps {
        sh 'python3 scripts/validate_restore.py'
      }
    }
    stage('Archive Logs') {
      steps {
        archiveArtifacts artifacts: 'reports/*.json, logs/*.log'
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every run is logged, every report is archived. When auditors ask if restores work — you show them a report with timestamps, counts, and index diffs. Not a gut feeling.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;lessons-learned-1&quot;&gt;Lessons Learned&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Automate infra, not just the restore.&lt;/strong&gt; Terraform gives you a clean slate every drill. Manual infra setup introduces variability that hides real problems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Validation is not optional.&lt;/strong&gt; A restore that &quot;seems fine&quot; is not the same as a restore that &lt;em&gt;is&lt;/em&gt; fine. Document count mismatches and missing indexes are easy to catch automatically and impossible to catch by eyeballing logs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Logs equal trust.&lt;/strong&gt; The audit trail is what makes your DR process credible to others — engineers, management, auditors. Without it, you're asking people to take your word for it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Minimal input reduces errors.&lt;/strong&gt; We trimmed required inputs to just host + DB name and let scripts infer the rest. Less to type = fewer mistakes under pressure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Practice makes permanent.&lt;/strong&gt; Each drill found a small improvement. After ten drills, the process was genuinely fast and boring — which is exactly what you want.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-outcome&quot;&gt;The Outcome&lt;/h3&gt;
&lt;p&gt;We went from a 3-hour manual war room exercise to a single Jenkins job anyone can trigger. The drills are now predictable, repeatable, and quick.&lt;/p&gt;
&lt;p&gt;More importantly — everyone on the team &lt;em&gt;believes&lt;/em&gt; the restores work, because the validation script proves it every single time.&lt;/p&gt;
&lt;p&gt;Boring DR is good DR.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Running MongoDB in production? When did you last drill a full restore? Drop your setup in the comments — curious how teams handle validation.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21768/mongodb-dr-drill-automation-with-terraform-python-jenkins-how-we-made-restores-boring</guid>
<pubDate>Thu, 02 Jul 2026 15:09:45 +0000</pubDate>
</item>
<item>
<title>🏗️ CasePilot — India's AI Legal Case Worker</title>
<link>https://coderlegion.com/19963/casepilot-indias-ai-legal-case-worker</link>
<description>&lt;p&gt;Most Indians facing landlord fraud, consumer scams, or workplace violations have no idea where to start. Legal fees cost more than the dispute is worth. CasePilot changes that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt;&lt;br&gt;
Describe your legal situation in plain language → 7 AI agents investigate your case → get a ready-to-send legal notice in minutes, for free.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The stack:&lt;/strong&gt;&lt;br&gt;
→ Google ADK — 7-agent pipeline (Intake → Law Research → Evidence → Strategy → Citation → Drafting → Memory)&lt;br&gt;
→ MongoDB Atlas Vector Search — 3,310 chunks across 13 Indian laws, semantic search at 3,072 dims&lt;br&gt;
→ MongoDB MCP Server — agents query Atlas via MCP protocol&lt;br&gt;
→ Gemini 3.1 Flash-Lite — reasoning across all agents&lt;br&gt;
→ Google Cloud Run — live public API&lt;br&gt;
→ gemini-embedding-2 — document + law corpus embeddings&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What makes it different from legal chatbots:&lt;/strong&gt;&lt;br&gt;
❌ Not: &quot;Here's what Section 108 says&quot;&lt;br&gt;
✅ Yes: &quot;Your landlord violated TPA S.108. Here's a pre-filled legal notice. Send it today. File consumer complaint in 15 days if no response.&quot;&lt;/p&gt;
&lt;p&gt;Evidence uploaded → Justice Score calculated → action timeline built → case persists across sessions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://casepilot-34309631370.us-central1.run.app&quot;&gt;https://casepilot-34309631370.us-central1.run.app&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Built solo for the Google Cloud Rapid Agent Hackathon — MongoDB track.&lt;/p&gt;
&lt;p&gt;GitHub: github.com/ssurekumar01111-hue/casepilot&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19963/casepilot-indias-ai-legal-case-worker</guid>
<pubDate>Mon, 08 Jun 2026 12:16:33 +0000</pubDate>
</item>
<item>
<title>From CRUD APIs to Production-Style Backend Architecture: My Video Sharing Platform Project</title>
<link>https://coderlegion.com/19204/from-crud-apis-to-production-style-backend-architecture-video-sharing-platform-project</link>
<description>&lt;p&gt; Completed a Production-Style Video Sharing Platform Backend API&lt;br&gt;
Over the past few weeks, I've been building a backend system inspired by modern video-sharing platforms using Node.js, Express.js, MongoDB, and Cloudinary.&lt;/p&gt;
&lt;p&gt;This project went far beyond CRUD operations and helped me understand how real backend systems are designed.&lt;/p&gt;
&lt;p&gt;Features Implemented:&lt;br&gt;
✅ JWT Authentication &amp;amp; Refresh Token Rotation&lt;br&gt;
✅ Secure HTTP-Only Cookie Based Sessions&lt;br&gt;
✅ Video Upload &amp;amp; Cloudinary Integration&lt;br&gt;
✅ Comment System&lt;br&gt;
✅ Like System (Videos, Comments, Tweets)&lt;br&gt;
✅ Subscription System&lt;br&gt;
✅ Playlist Management&lt;br&gt;
✅ Watch History Tracking&lt;br&gt;
✅ Creator Dashboard Analytics&lt;br&gt;
✅ Pagination &amp;amp; Aggregation Pipelines&lt;/p&gt;
&lt;p&gt;Key Things I Learned:&lt;br&gt;
 Authentication using Access &amp;amp; Refresh Tokens&lt;br&gt;
 Designing relationships in MongoDB&lt;br&gt;
 MongoDB Aggregation Pipelines ($lookup, $group, $project, $addFields)&lt;br&gt;
 Ownership-Based Authorization&lt;br&gt;
 Cloudinary Media Management&lt;br&gt;
 Cascade Cleanup Logic&lt;br&gt;
 Building consistent API response structures&lt;/p&gt;
&lt;p&gt;Interesting Debugging Challenges:&lt;br&gt;
Duplicate MongoDB index issues&lt;br&gt;
Populate path errors&lt;br&gt;
Refresh token validation flow&lt;br&gt;
Route mismatches&lt;br&gt;
Aggregation pipeline bugs&lt;br&gt;
Watch history synchronization&lt;br&gt;
Video deletion cleanup across collections&lt;/p&gt;
&lt;p&gt;Documentation Created:&lt;br&gt;
 Complete API Documentation&lt;br&gt;
 Authentication Flow Documentation&lt;br&gt;
️ Database Schema Documentation&lt;/p&gt;
&lt;p&gt;What's Next?&lt;br&gt;
Building the React frontend and converting this into a complete full-stack application.&lt;/p&gt;
&lt;p&gt;GitHub Repository: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/SairajJadhav08/Video-Sharing-Platform-API&quot;&gt;https://github.com/SairajJadhav08/Video-Sharing-Platform-API&lt;/a&gt;&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/19204/from-crud-apis-to-production-style-backend-architecture-video-sharing-platform-project</guid>
<pubDate>Fri, 29 May 2026 09:30:58 +0000</pubDate>
</item>
<item>
<title>Tooling Tips: Visualizing Your Data in MongoDB and Kafka</title>
<link>https://coderlegion.com/16796/tooling-tips-visualizing-your-data-in-mongodb-and-kafka</link>
<description>&lt;p&gt;Hey folks!&lt;/p&gt;
&lt;p&gt;Quick tooling post today. Before moving forward to the next services, I want to share the tools I use to &lt;strong&gt;visualize and inspect data&lt;/strong&gt; in MongoDB and Kafka during development.&lt;/p&gt;
&lt;p&gt;Without these tools, debugging an asynchronous data pipeline would be much harder — you're essentially &quot;blind&quot; to what's happening inside the containers.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;mongodb-mongodb-compass&quot;&gt;MongoDB — MongoDB Compass&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MongoDB Compass&lt;/strong&gt; is MongoDB's official GUI. Free and available for Windows, Mac and Linux.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What you can do:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Browse collections and documents&lt;/li&gt;
&lt;li&gt;Run queries with visual filters&lt;/li&gt;
&lt;li&gt;See indexes created by Flyway/code&lt;/li&gt;
&lt;li&gt;Inspect document schemas&lt;/li&gt;
&lt;li&gt;Monitor performance metrics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How to connect to the local container:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;URI: mongodb://root:password@localhost:27017/?authSource=admin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After connecting, you'll see the &lt;code&gt;market_data_db&lt;/code&gt; database with the &lt;code&gt;price_history&lt;/code&gt; collection being populated by &lt;code&gt;broker-market-data-api&lt;/code&gt; on each ingestion round.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Download:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.mongodb.com/products/tools/compass&quot;&gt;mongodb.com/products/tools/compass&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;kafka-offset-explorer-formerly-kafka-tool-&quot;&gt;Kafka — Offset Explorer (formerly Kafka Tool)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Offset Explorer&lt;/strong&gt; is a GUI for inspecting Kafka clusters. It lets you see topics, partitions, messages and offsets without needing the command line.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What you can do:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;List all topics in the cluster&lt;/li&gt;
&lt;li&gt;See messages from each partition in real time&lt;/li&gt;
&lt;li&gt;Inspect the content (key + value) of each message&lt;/li&gt;
&lt;li&gt;Monitor consumer group offsets&lt;/li&gt;
&lt;li&gt;Check whether consumers have lag&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How to connect to the local container:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-conf&quot;&gt;Bootstrap Servers: localhost:9092
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After connecting, you'll see the &lt;code&gt;trading-assets-market-data-v1&lt;/code&gt; topic with messages published by &lt;code&gt;broker-market-data-api&lt;/code&gt;. Each message will have the ticker as key (e.g. &lt;code&gt;PETR4&lt;/code&gt;) and the quote JSON as value.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Download:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.kafkatool.com&quot;&gt;kafkatool.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;rabbitmq-management-ui-already-included-&quot;&gt;RabbitMQ — Management UI (already included)&lt;/h2&gt;
&lt;p&gt;RabbitMQ comes with a built-in web interface. In our &lt;code&gt;docker-compose.yml&lt;/code&gt;, port &lt;code&gt;15672&lt;/code&gt; is exposed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;URL: http://localhost:15672
User: admin
Pass: admin_pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What you can do:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;View queues, exchanges and bindings&lt;/li&gt;
&lt;li&gt;Inspect messages in queues&lt;/li&gt;
&lt;li&gt;Manually publish messages to test consumers&lt;/li&gt;
&lt;li&gt;Monitor message rates and lag&lt;/li&gt;
&lt;li&gt;See the DLQ (Dead Letter Queue) when messages fail&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the most useful tool for debugging the flow between &lt;code&gt;trading-broker-order&lt;/code&gt; and &lt;code&gt;b3-matching-engine&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;workflow-tip&quot;&gt;Workflow Tip&lt;/h2&gt;
&lt;p&gt;During development, I use all three tools in parallel:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Offset Explorer&lt;/strong&gt; to confirm &lt;code&gt;broker-market-data-api&lt;/code&gt; is publishing messages to Kafka&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MongoDB Compass&lt;/strong&gt; to confirm the history is being persisted&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RabbitMQ Management&lt;/strong&gt; to confirm orders are arriving in the B3 queues&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This turns an opaque asynchronous pipeline into something completely observable.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-s-next-&quot;&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;With the tools set up, in the next post we build &lt;code&gt;b3-market-sync&lt;/code&gt; — the Java service that consumes quotes from Kafka and synchronizes them to Redis, feeding the Matching Engine with real-time prices.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;about-the-series&quot;&gt;About the Series&lt;/h2&gt;
&lt;p&gt;⬅️ Previous Post: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/market-data-integrator-consuming-real-time-data-with-python-mongodb-and-kafka-8k1&quot;&gt;Market Data Integrator&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;➡️ Next Post: Syncing the Real Market: Consuming Brapi and Feeding Redis with Spring Boot&lt;/p&gt;
&lt;p&gt; Series Index: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/series-roadmap-building-a-stock-brokerage-simulator-with-microservices-kgh&quot;&gt;Series Roadmap&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/roberto-de-vargas/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16796/tooling-tips-visualizing-your-data-in-mongodb-and-kafka</guid>
<pubDate>Wed, 06 May 2026 00:32:14 +0000</pubDate>
</item>
<item>
<title>Show &amp; Tell: Building a Self-Healing SEO Architecture for a Vue SPA</title>
<link>https://coderlegion.com/16347/show-tell-building-a-self-healing-seo-architecture-for-a-vue-spa</link>
<description>&lt;p&gt;Hey CoderLegion!&lt;/p&gt;
&lt;p&gt;I recently tackled a massive SEO and scaling hurdle for my app, Recipe Finder, and wanted to share the architecture breakdown with the community. If you're building SPAs and relying on external APIs, you've probably run into this exact nightmare.&lt;/p&gt;
&lt;p&gt;Building a modern Single Page Application (SPA) with Vite and Vue is great for user experience, but it's a minefield for SEO. We faced three major hurdles:&lt;/p&gt;
&lt;p&gt;Aggressive Bot Protection: Our .htaccess was so tight it was blocking crawlers that we actually wanted.&lt;/p&gt;
&lt;p&gt;The &quot;SPA Meta Trap&quot;: Social media bots (Facebook, WhatsApp) couldn't read our dynamic recipe titles or images because they don't execute JavaScript.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Scale Problem: We have access to millions of recipes via the Spoonacular API, but we don't own the full database. How do you tell Google about millions of pages you don't physically store?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;️ The Tech Stack&lt;br&gt;
Frontend: Vite + Vue 3 (Hosted on Apache)&lt;/p&gt;
&lt;p&gt;Backend: Node.js + Express (Hosted on Firebase Functions)&lt;/p&gt;
&lt;p&gt;Database: MongoDB&lt;/p&gt;
&lt;p&gt;Provider: Spoonacular API&lt;/p&gt;
&lt;p&gt; The Solution: A 3-Step &quot;Self-Healing&quot; Architecture&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Solving the Social Preview (The Meta Injection)&lt;br&gt;
Since our frontend is on a standard Apache host, we couldn't use edge functions easily. Instead, we optimized our URL structure to include SEO-friendly slugs:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://recipe-finder.org/recipe/644488-german-rhubarb-cake-with-meringue&quot;&gt;recipe-finder.org/recipe/644488-german-rhubarb-cake-with-meringue&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We then implemented a backend-driven meta-injection strategy. When a recipe is requested, our Express server pre-fills the Open Graph tags (og:title, og:image, og:description) using the recipe summary, ensuring beautiful previews on social media.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &quot;Self-Building&quot; Database&lt;br&gt;
We didn't want to scrape millions of recipes (and get banned). Instead, we created an Organic Growth Engine.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Every time a user (guest or authenticated) clicks a recipe, our Express backend performs an Upsert into MongoDB. If it's a new recipe, it's added to our &quot;SEO Index.&quot; If it's an existing one, we update the lastViewed timestamp.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;JavaScript
// Remove stale entry, then push back to front with a fresh timestamp
await recipeViewedModel.findOneAndUpdate(
  { auth0Id },
  { $pull: { recipes: { id: recipe.id } } }
);

await recipeViewedModel.findOneAndUpdate(
  { auth0Id },
  { $push: { recipes: { $each: [{ ...recipe, viewedAt: new Date() }], $position: 0 } } },
  { upsert: true, new: true }
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ensures our database only grows with high-quality, relevant content that users actually care about.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Dynamic Hybrid Sitemap&lt;br&gt;
A static sitemap.xml was impossible for millions of potential links. We built a Dynamic Sitemap Index:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;sitemap-main.xml — A static file on our hosting server for core pages (Home, Tools, About).&lt;/p&gt;
&lt;p&gt;sitemap-recipes-[n].xml — Dynamic routes on Express that query MongoDB and generate XML on the fly in 50,000-unit chunks.&lt;/p&gt;
&lt;p&gt;The Master Index — A central sitemap.xml that bridges the two, served via a silent proxy in .htaccess.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Apache
RewriteRule ^sitemap\.xml$ https://your-region-your-project.cloudfunctions.net/api/sitemap.xml [R=301,L]
RewriteRule ^sitemap-recipes-([0-9]+)\.xml$ https://your-region-your-project.cloudfunctions.net/api/sitemap-recipes-$1.xml [R=301,L]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any request for a sitemap is silently routed to the Express API, which assembles the XML from MongoDB on the fly — no static file maintenance required.&lt;/p&gt;
&lt;p&gt; The Results&lt;br&gt;
Google Search Console Verified: Live URL testing shows Google successfully rendering the SPA and reading the dynamic content.&lt;/p&gt;
&lt;p&gt;Automated SEO: The more our users cook, the larger our sitemap grows. We don't have to manually add a single link.&lt;/p&gt;
&lt;p&gt;Zero-Maintenance Scaling: The system handles 10 recipes or 10 million with the same memory footprint thanks to MongoDB's $group and $limit aggregations.&lt;/p&gt;
&lt;p&gt; Key Takeaway for CoderLegion&lt;br&gt;
Don't build for millions of pages on Day 1. Build a system that lets your users' activity grow your SEO footprint for you. Work with the bots, not against them.&lt;/p&gt;
&lt;p&gt;Author: &lt;a rel=&quot;nofollow&quot; href=&quot;https://rusu-ionut.ro&quot;&gt;Rusu Ionut&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Project: &lt;a rel=&quot;nofollow&quot; href=&quot;https://recipe-finder.org&quot;&gt;recipe-finder.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Discussion: How is everyone else handling SEO for large-scale SP&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16347/show-tell-building-a-self-healing-seo-architecture-for-a-vue-spa</guid>
<pubDate>Fri, 01 May 2026 18:26:49 +0000</pubDate>
</item>
<item>
<title>Created a new feature on Recipe-Finder.org, FREE Month of Premium(https://recipe-finder.org/invite-)</title>
<link>https://coderlegion.com/16147/created-new-feature-recipe-finder-org-free-month-of-premium-https-recipe-finder-org-invite</link>
<description>&lt;p&gt;We recently built the referral page for Recipe Finder: &lt;a rel=&quot;nofollow&quot; href=&quot;https://recipe-finder.org/invite-one-free-month&quot;&gt;https://recipe-finder.org/invite-one-free-month&lt;/a&gt;. The goal was to make referrals feel like a real product feature, not just a static landing page. &lt;/p&gt;
&lt;p&gt;On the frontend, we used &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue 3 &lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;Vite&lt;/li&gt;
&lt;li&gt;Vuetify&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;to build the page and referral UI. &lt;br&gt;
For auth we used &lt;strong&gt;Auth0&lt;/strong&gt;, and on the backend we used &lt;strong&gt;Node.js + Express&lt;/strong&gt;, deployed with Firebase Functions, with &lt;strong&gt;MongoDB/Mongoose handling referral codes&lt;/strong&gt;, invite tracking, and reward state. &lt;br&gt;
The flow is simple: a user shares their referral link, the app captures the code from the URL, a new user claims it securely, and once &lt;strong&gt;Stripe confirms a paid Premium signup&lt;/strong&gt;, we automatically extend the inviter’s Premium by 30 days.&lt;/p&gt;
&lt;p&gt;Give it a try: &lt;a rel=&quot;nofollow&quot; href=&quot;https://recipe-finder.org/invite-one-free-month&quot;&gt;https://recipe-finder.org/invite-one-free-month&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16147/created-new-feature-recipe-finder-org-free-month-of-premium-https-recipe-finder-org-invite</guid>
<pubDate>Wed, 29 Apr 2026 06:35:59 +0000</pubDate>
</item>
<item>
<title>From localStorage to Database: Understanding the MongoDB Structure</title>
<link>https://coderlegion.com/7513/from-localstorage-to-database-understanding-the-mongodb-structure</link>
<description>&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;/h2&gt;
&lt;p&gt;Hey everyone! If you are a developer who is &lt;strong&gt;just starting&lt;/strong&gt; to work with databases, or perhaps you already know the basics and are looking for a recap or a &lt;strong&gt;more in-deph&lt;/strong&gt; understanding, I hope this article will help you.&lt;br&gt;
When I learn something new, I like to dig deeper and  understand the &lt;strong&gt;underlying logic&lt;/strong&gt; to ensure everything is clear. That's why I have structured this article starting from the &lt;code&gt;NoSQL&lt;/code&gt; fundamentals all the way to the internal data storage mechanisms, concentrating on the &lt;strong&gt;why&lt;/strong&gt; behind the database. So, I wish you &lt;strong&gt;happy reading&lt;/strong&gt;!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In this article, we will study:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;&quot;&gt;Where we Started: The SQL World&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;&quot;&gt;The NoSQL story: Not Only SQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;&quot;&gt;From localStorage to Database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;&quot;&gt;Basic CRUD Operations: Create, Read, Update, Delete&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;#1. Where we Started: The SQL World&lt;/p&gt;
&lt;p&gt;When we work with Relational Database like &lt;code&gt;MySQL&lt;/code&gt; or &lt;code&gt;PostgreSQL&lt;/code&gt;, our data lines up neatly in those perfectly organized tables. We have to make sure &lt;strong&gt;every single row&lt;/strong&gt; follows a &lt;strong&gt;rigid&lt;/strong&gt; column blueprint, decided right from the start. If we ever need to &lt;strong&gt;switch up&lt;/strong&gt; that structure, we basically &lt;em&gt;hit the brakes&lt;/em&gt;, tackle complex changes, and cross our fingers that all the existing data &lt;strong&gt;still fits&lt;/strong&gt;. &lt;br&gt;
We got consistency and logic out of this, sure, but as our &lt;strong&gt;apps kept scaling&lt;/strong&gt;, that stiffnes became a real &lt;strong&gt;bottleneck&lt;/strong&gt; for moving fast.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;#2. The NoSQL story: Not Only SQL&lt;br&gt;
When it comes to &lt;code&gt;NoSQL&lt;/code&gt; databases, you might have heard that it means &lt;em&gt;No SQL&lt;/em&gt;, but let's clear things up: it's not a total war against &lt;code&gt;SQL&lt;/code&gt;! The correct and modern interpretation is &lt;em&gt;Not Only SQL&lt;/em&gt;❗&lt;/p&gt;
&lt;h5 id=&quot;why-not-only-sql-&quot;&gt;Why 'Not Only SQL'❓&lt;/h5&gt;
&lt;p&gt;Because we relize that &lt;code&gt;SQL&lt;/code&gt; is a fantastic certain tasks (where strict relationship and consistency are &lt;strong&gt;vital&lt;/strong&gt;), today's modern applications (social networks, gaming, websites) demand &lt;strong&gt;speed&lt;/strong&gt; and &lt;strong&gt;flexibility&lt;/strong&gt;. &lt;code&gt;NoSQL&lt;/code&gt; is a supplement, an alternative that allows us to choose the right tool for the right job. We &lt;strong&gt;aren't giving up&lt;/strong&gt; on &lt;code&gt;SQL&lt;/code&gt;, we're just &lt;strong&gt;adding&lt;/strong&gt; more powerful to our toolkit.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;#3. From localStorage to Database&lt;br&gt;
Among the many types of &lt;code&gt;NoSQL&lt;/code&gt; databases, MongoDB falls in to the &lt;em&gt;Document-Oryented&lt;/em&gt; category.&lt;/p&gt;
&lt;p&gt;Imagne no longer storing data across separate rows and columns. Instead, you store a &lt;strong&gt;complete record&lt;/strong&gt;, for example, a user profile with all their addresses, orders, and preferences, in a single package called a &lt;strong&gt;Document&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;document-&quot;&gt;Document:&lt;/h4&gt;
&lt;p&gt;→ The Document is the basic &lt;strong&gt;building block&lt;/strong&gt;. It looks exactly like a &lt;code&gt;JSON&lt;/code&gt; (JavaScript Object Notation) object, but, technically speacking, MongoDB uses &lt;code&gt;BSON&lt;/code&gt; (Binary &lt;code&gt;JSON&lt;/code&gt;). Why BSON❓ Because it's a &lt;strong&gt;faster binary format&lt;/strong&gt; that also includes &lt;strong&gt;data&lt;/strong&gt; types that simple &lt;code&gt;JSON&lt;/code&gt; dosen't have (like date/ time values).&lt;/p&gt;
&lt;p&gt;→ All these Documents (which, note, &lt;strong&gt;do not have to look identical&lt;/strong&gt;) are grouped into a &lt;strong&gt;Collection&lt;/strong&gt;, a kind of &lt;em&gt;folder&lt;/em&gt; that replaces the old &lt;em&gt;tables.&lt;/em&gt; To get an idea, we can think of documents &lt;strong&gt;as the value&lt;/strong&gt; stored in &lt;code&gt;localStorage&lt;/code&gt;, but they are much more powerful than a simple string value in &lt;code&gt;localStorage&lt;/code&gt;❗&lt;/p&gt;
&lt;p&gt;→ The Document is the &lt;strong&gt;smallest&lt;/strong&gt; and &lt;strong&gt;most important&lt;/strong&gt; unit of data in MongoDB, being the complete package of information that offers tremendous flexibility by &lt;strong&gt;allowing&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;→ Storing &lt;strong&gt;nested objects&lt;/strong&gt;,&lt;br&gt;
→ Storing &lt;strong&gt;lists&lt;/strong&gt;,&lt;br&gt;
→ Direct storage of &lt;strong&gt;complex data types&lt;/strong&gt; (such as Date/Time).&lt;/p&gt;
&lt;p&gt;Here we have a &lt;code&gt;Document&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;
{    // unique ID 
  &quot;_id&quot;: ObjectId(&quot;61a5c6f0e8f0b7c4d5a9b8c7&quot;),
     // complex data type: Date/Time
  &quot;order_date&quot;: ISODate(&quot;2025-11-18T10:00:00Z&quot;), 
  &quot;status&quot;: &quot;Shipped&quot;,
      // nested object
  &quot;shipping_address&quot;: {
    &quot;street&quot;: &quot;10 Example Blvd&quot;,
    &quot;city&quot;: &quot;London&quot;,
    &quot;zip_code&quot;: &quot;SW1A 0AA&quot;
  },
    // list (Array)
  &quot;items&quot;: [
    {
      &quot;product_name&quot;: &quot;Laptop Pro&quot;,
      &quot;quantity&quot;: 1,
      &quot;price&quot;: 1200.00
    },
    {
      &quot;product_name&quot;: &quot;Mouse Wireless&quot;,
      &quot;quantity&quot;: 2,
      &quot;price&quot;: 25.50
    }
  ]
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;colection-&quot;&gt;Colection:&lt;/h4&gt;
&lt;p&gt;→ If a Document is the equivalent of an individual record (like a complex &lt;strong&gt;value stored&lt;/strong&gt; in &lt;code&gt;localStorage&lt;/code&gt; or a &lt;strong&gt;row&lt;/strong&gt; in a &lt;code&gt;SQL&lt;/code&gt; table), the Collection in MongoDB is the &lt;em&gt;folder&lt;/em&gt; that holds all those records together.&lt;/p&gt;
&lt;p&gt;→The &lt;code&gt;Collection&lt;/code&gt; is the conceptual &lt;strong&gt;equivalent of a Table&lt;/strong&gt; in a relational (&lt;code&gt;SQL&lt;/code&gt;) database. Unlike a &lt;code&gt;SQL&lt;/code&gt; table, where &lt;strong&gt;all rows&lt;/strong&gt; must adhere to the &lt;strong&gt;same structure&lt;/strong&gt; (the same columns), &lt;code&gt;Documents&lt;/code&gt; within a MongoDB &lt;code&gt;Collection&lt;/code&gt; can have different structures (dynamic schema)❗&lt;/p&gt;
&lt;p&gt;→ A small example, In MongoDB, we have a Database named &lt;code&gt;school&lt;/code&gt;. This Database contains two &lt;code&gt;Collections&lt;/code&gt;: students and teachers.&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rw7qquucdkmjg6wgfwep.png&quot; alt=&quot;Image description&quot;&gt;,&lt;/p&gt;
&lt;p&gt;→ 1. The &lt;code&gt;students&lt;/code&gt; Collection contains two documents:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;  {
    first_name: &quot;Alice&quot;,
    last_name: &quot;Smith&quot;,
    age: 16,
    grade: 10,
    enrollment_date: new Date(&quot;2024-09-01T00:00:00Z&quot;),
    courses: [&quot;Math&quot;, &quot;Physics&quot;, &quot;English&quot;] // Array
  },

  {
    first_name: &quot;Bob&quot;,
    last_name: &quot;Johnson&quot;,
    age: 17,
    grade: 11,
    enrollment_date: new Date(&quot;2023-09-01T00:00:00Z&quot;),
    contact: { // nested object
      phone: &quot;555-1234&quot;,
      email: &quot;*Emails are not allowed*&quot;
    }
  }

&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;The teachers &lt;code&gt;Collection&lt;/code&gt; contains two documents: &lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt; {
    title: &quot;Mr.&quot;,
    last_name: &quot;Davis&quot;,
    subject: &quot;Mathematics&quot;,
    tenure: true,
    phone: &quot;555-5678&quot;
    class_count: 5
  },
 
  {
    title: &quot;Ms.&quot;,
    last_name: &quot;Garcia&quot;,
    subject: &quot;History&quot;,
    class_count: 4,
    office_hours: { // Nested Object
      day: &quot;Wednesday&quot;,
      time: &quot;2:00 PM - 3:00 PM&quot;
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;flexibility-by-nature-&quot;&gt;Flexibility by Nature:&lt;/h4&gt;
&lt;p&gt;→ One user document might have a &lt;code&gt;phone&lt;/code&gt; field, while the next one &lt;strong&gt;might not&lt;/strong&gt;. This dynamic schema is the &lt;strong&gt;main reason&lt;/strong&gt; developers choose MongoDB when dealing with data that &lt;strong&gt;evolves rapidly&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;→ Therefore, MongoDB allows us &lt;strong&gt;to model data naturally&lt;/strong&gt;, like objects (documents), and these objects are managed efficiently within &lt;strong&gt;logical groups&lt;/strong&gt; called &lt;code&gt;Collections&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-essential-field-id&quot;&gt;The Essential Field: &lt;code&gt;_id&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;→ Every Document must have a unique field named &lt;code&gt;_id&lt;/code&gt;. This field serves as the &lt;strong&gt;Primary Key&lt;/strong&gt; for the document (just like the unique ID in a SQL row).&lt;/p&gt;
&lt;p&gt;→ If you don't specify an _id yourself when inserting data, MongoDB automatically generates one for you using a special data type called &lt;strong&gt;ObjectId&lt;/strong&gt;. This ObjectId is guaranteed &lt;strong&gt;to be unique&lt;/strong&gt; and even includes a &lt;strong&gt;timestamp&lt;/strong&gt;, giving you information about the exact moment the document &lt;strong&gt;was created&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;#4. Basic CRUD Operations: &lt;br&gt;
(Assuming the MongoDB server is installed and mongosh is ready, we can now connect to the database to see how we create collections and documents)...&lt;/p&gt;
&lt;p&gt;→ &lt;code&gt;CRUD&lt;/code&gt; operations represent the &lt;strong&gt;four basic functions&lt;/strong&gt; that underlie any persistent &lt;strong&gt;data storage&lt;/strong&gt; system. In the context of a database (such as MongoDB, although the terms apply generally), these are the methods through which we &lt;strong&gt;interact&lt;/strong&gt; with &lt;code&gt;Documents&lt;/code&gt; within a &lt;code&gt;Collection&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;1-create-insertone-insertmany-&quot;&gt;1. Create: &lt;code&gt;insertOne()&lt;/code&gt;/&lt;code&gt;insertMany()&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;→ The &lt;code&gt;Create&lt;/code&gt; operation adds new documents into a collection.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;insertOne(document)&lt;/code&gt;: this method is used to insert a &lt;strong&gt;single new document&lt;/strong&gt; into the collection.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;  //this will add a new document in 
  //teachers collection
db.teachers.insertOne({
  title: &quot;Mr.&quot;,
  last_name: &quot;Jack&quot;,
  subject: &quot;Geography&quot;,
  tenure: false,
  phone: &quot;555-5566&quot;
  class_count: 6
})

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;insertMany([document1, document2, ...])&lt;/code&gt;: this method is used to insert &lt;strong&gt;multiple documents&lt;/strong&gt; into the collection with a single command. The argument is an array (list) of documents.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;  //this will add two documents in 
  //students collection
db.students.insertMany([
   {
     first_name: &quot;Lea&quot;,
     last_name: &quot;Casy&quot;,
     age: 17,
     grade: 10
   },
   {
     first_name: &quot;Jessy&quot;,
     last_name: &quot;Smith&quot;,
     age: 18,
     grade: 10
   }
])

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-read-find-findone-&quot;&gt;2. Read: &lt;code&gt;find()&lt;/code&gt;/&lt;code&gt;findOne()&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;→ The &lt;code&gt;Read&lt;/code&gt; operation retrieves documents from the collection based on filtering criteria.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;findOne(query)&lt;/code&gt;: this method returns the &lt;strong&gt;first document&lt;/strong&gt; that matches the filtering criteria specified in the query object. It is ideal when you know you need a single document, for example, based on a unique ID. If &lt;strong&gt;no query is specified&lt;/strong&gt; (i.e., it is empty: {}), it returns the &lt;strong&gt;first document&lt;/strong&gt; found in the collection.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;//find the first student with name 'Lea'
db.students.findOne({ first_name: 'Lea' })

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;find(query)&lt;/code&gt;: This method returns a cursor to all documents that match the specified filtering criteria. If the query object is empty ({}), it returns all documents in the collection.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;//return all of documents from students colection
db.students.find()

//find all students whose age is greater than 16
db.students.find({ age: {$gt: 16 }})
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-update-updateone-updatemany-&quot;&gt;3. Update: updateOne()/updateMany()&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;updateOne(filter, update_operation)&lt;/code&gt;: Updates the first document that matches the given filter.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;//finds the document with `first_name 'Jessy'` and
// sets the new grade to false.
db.students.updateOne(
  { first_name: 'Jessy' },
  { $set: { grade: false } }
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;updateMany(filter, update_operation)&lt;/code&gt;: Updates all documents that match the given filter.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// It selects documents only if the field contact is absent. ($exists: false)
// and add the 'contact' object with default phone and email values.
db.students.updateMany(
  // 1. FILTER: Select documents missing the 'contact' field
  { contact: { $exists: false } }, 
  
  // 2. OPERATION: Use $set to add the new 'contact' object
  { 
    $set: { 
      &quot;contact.phone&quot;: false, 
      &quot;contact.email&quot;: &quot;*Emails are not allowed*&quot; 
    } 
  }
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;→ The Basic &lt;code&gt;$set&lt;/code&gt; Operator: This is the most common update operator. It specifies &lt;strong&gt;which fields&lt;/strong&gt; should be modified and what their new values are. It either &lt;strong&gt;replaces&lt;/strong&gt; the value of an existing field or &lt;strong&gt;adds&lt;/strong&gt; a new field if it doesn't exist.&lt;/p&gt;
&lt;p&gt;Basic Syntax: In update calls, the update operation part will often look like this: &lt;code&gt;{&quot;$set&quot;: {&quot;field_name&quot;: &quot;new_value&quot;, &quot;another_field&quot;: 123}}&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;4-delete-deleteone-deletemany&quot;&gt;4.Delete: deleteOne()/deleteMany&lt;/h4&gt;
&lt;p&gt;→ The Delete operation removes documents from the collection.&lt;br&gt;
&lt;code&gt;deleteOne(filter)&lt;/code&gt;: deletes the &lt;strong&gt;first document&lt;/strong&gt; that matches the specified filter.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// delete the first student found whose first name is 'Lea'.
db.students.deleteOne({ first_name: &quot;Lea&quot; })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;deleteMany(filter)&lt;/code&gt;: deletes &lt;strong&gt;all&lt;/strong&gt; documents that match the specified filter.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// delete all students who are currently in grade 10.
db.students.deleteMany({ grade: 10 });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Caution❗If you use &lt;code&gt;deleteMany({})&lt;/code&gt; (an empty filter), you will &lt;strong&gt;delete all&lt;/strong&gt; documents in the collection. Always be careful when using delete operations.&lt;/p&gt;
&lt;p&gt;Delete a colection: &lt;code&gt;db.students.drop()&lt;/code&gt;,&lt;br&gt;
Delete a database: first select the database &lt;code&gt;use school&lt;/code&gt;, then we delete it &lt;code&gt;db.dropDatabase()&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion-&quot;&gt;Conclusion ❤️&lt;/h3&gt;
&lt;p&gt;While I haven't delved into the specifics of working tools, my goal for this first article was to provide a short but &lt;strong&gt;solid introduction&lt;/strong&gt; to the fundamental of MongoDB.&lt;/p&gt;
&lt;p&gt;There is a lot to explore in the world of MongoDB, but I believe these basics are essential. If you understand how data saving and retrieval work in localStorage, you can draw a &lt;strong&gt;useful analogy&lt;/strong&gt; to better grasp collections within a database. &lt;br&gt;
I will be back soon with more detailed posts on the subject, further deepening the concepts we've learned. I hope this introductory guide was helpful!&lt;/p&gt;
&lt;p&gt;If you &lt;strong&gt;enjoyed&lt;/strong&gt; this article, learned something new, or if it brought back a &lt;strong&gt;smile&lt;/strong&gt; thinking about your &lt;strong&gt;first attempts&lt;/strong&gt; in MongoDB, don't forget to like the post or leave a comment! &lt;strong&gt;Thank you so much&lt;/strong&gt; for reading and spending your valuable time with this article. I look forward to connecting with you &lt;strong&gt;in the next one&lt;/strong&gt;! Happy codding!&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/7513/from-localstorage-to-database-understanding-the-mongodb-structure</guid>
<pubDate>Wed, 19 Nov 2025 08:09:32 +0000</pubDate>
</item>
<item>
<title>Local MongoDB like database in flutter</title>
<link>https://coderlegion.com/2868/local-mongodb-like-database-in-flutter</link>
<description>&lt;h3 id=&quot;flutterdb&quot;&gt;FlutterDB&lt;/h3&gt;
&lt;p&gt;FlutterDB is a lightweight, SQLite-based document database for Flutter applications. It provides a MongoDB-like API for managing collections and documents while leveraging the performance benefits of SQLite.&lt;/p&gt;
&lt;h4 id=&quot;why-do-i-build-this-&quot;&gt;Why do I build this?&lt;/h4&gt;
&lt;p&gt;So I am using MongoDB in my NodeJS application. Now I am building a chat application in Flutter, and for rendering performance, I thought I should store the messages locally and render a sliding window on it. Still, I have no luck finding a good local database that is flexible enough and also fast enough. And so I thought, why not build a MongoDB-like database in Flutter, which gets the job done. It's not a full-fledged MongoDB running locally, but it is similar to it, and most of the common tasks are going very well, as I tested.&lt;/p&gt;
&lt;h4 id=&quot;features&quot;&gt;Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Document-oriented database with collections and documents&lt;/li&gt;
&lt;li&gt;MongoDB-style query syntax and operations&lt;/li&gt;
&lt;li&gt;Built on top of SQLite for performance and reliability&lt;/li&gt;
&lt;li&gt;Unique ID generation for documents&lt;/li&gt;
&lt;li&gt;Support for complex queries, updates, and aggregations&lt;/li&gt;
&lt;li&gt;Batch operations for better performance&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;installation&quot;&gt;Installation&lt;/h4&gt;
&lt;p&gt;Add the following dependencies to your &lt;code&gt;pubspec.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;dependencies:
  flutterdb: ^0.0.1
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h4&gt;
&lt;p&gt;Import the package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;import 'package:flutterdb/flutterdb.dart';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Initialize the database:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;final db = FlutterDB();
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;basic-usage&quot;&gt;Basic Usage&lt;/h4&gt;
&lt;h5 id=&quot;working-with-collections&quot;&gt;Working with Collections&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;// Create or get a collection
final users = await db.collection('users');

// List all collections
final collections = await db.listCollections();

// Drop a collection
await db.dropCollection('users');
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;crud-operations&quot;&gt;CRUD Operations&lt;/h4&gt;
&lt;h5 id=&quot;create&quot;&gt;Create&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;// Insert a single document
final userId = await users.insert({
  'name': 'John Doe',
  'email': '*Emails are not allowed*',
  'age': 30
});

// Insert multiple documents
final ids = await users.insertMany([
  {
    'name': 'Jane Smith',
    'email': '*Emails are not allowed*',
    'age': 25
  },
  {
    'name': 'Bob Johnson',
    'email': '*Emails are not allowed*',
    'age': 40
  }
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;read&quot;&gt;Read&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;// Find all documents in a collection
final allUsers = await users.find({});

// Find documents with a simple query
final adults = await users.find({'age': {'$gte': 18}});

// Find a document by ID
final user = await users.findById(userId);

// Count documents
final userCount = await users.count({'age': {'$gt': 30}});
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;update&quot;&gt;Update&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;// Update a document by ID
await users.updateById(userId, {'status': 'active'});

// Update multiple documents
final updatedCount = await users.updateMany(
  {'age': {'$lt': 30}},
  {'status': 'young'}
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;delete&quot;&gt;Delete&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;// Delete a document by ID
await users.deleteById(userId);

// Delete multiple documents
final deletedCount = await users.deleteMany({'status': 'inactive'});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;advanced-queries&quot;&gt;Advanced Queries&lt;/h3&gt;
&lt;h4 id=&quot;comparison-operators&quot;&gt;Comparison Operators&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;// Greater than
await users.find({'age': {'$gt': 30}});

// Less than or equal
await users.find({'age': {'$lte': 25}});

// Not equal
await users.find({'status': {'$ne': 'inactive'}});

// In array
await users.find({'role': {'$in': ['admin', 'moderator']}});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;logical-operators&quot;&gt;Logical Operators&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;// AND
await users.find({
  '$and': [
    {'age': {'$gte': 18}},
    {'status': 'active'}
  ]
});

// OR
await users.find({
  '$or': [
    {'role': 'admin'},
    {'permissions': {'$in': ['write', 'delete']}}
  ]
});

// NOR
await users.find({
  '$nor': [
    {'status': 'banned'},
    {'role': 'guest'}
  ]
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;text-search&quot;&gt;Text Search&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;// Regex search
await users.find({'name': {'$regex': '^Jo'}});

// Simple text search
await users.find({'bio': {'$like': 'flutter developer'}});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;aggregation&quot;&gt;Aggregation&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;final results = await users.aggregate([
  {'$match': {'status': 'active'}},
  {'$sort': {'age': -1}},
  {'$skip': 10},
  {'$limit': 20},
  {'$project': {
          'name': 1,
          'age': 1,
          'city': 1,
          '_id': 0,
               }
  },
  {'$group': {
          '_id': '\$city',
          'count': {'\$sum': 1}
        }
  },
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;best-practices&quot;&gt;Best Practices&lt;/h3&gt;
&lt;h4 id=&quot;performance-optimization&quot;&gt;Performance Optimization&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use batch operations&lt;/strong&gt; for multiple insertions or updates:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;await users.insertMany(manyUsers); // Better than inserting one by one
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep document size reasonable&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Large documents can slow down performance&lt;/li&gt;
&lt;li&gt;Consider storing large binary data (images, files) separately&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create indexes&lt;/strong&gt; for frequently queried fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Currently, FlutterDB automatically indexes collection names&lt;/li&gt;
&lt;li&gt;Future versions may support custom indexes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use appropriate queries&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Finding by ID is faster than complex queries&lt;/li&gt;
&lt;li&gt;Limit results when possible using &lt;code&gt;$limit&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;data-structure&quot;&gt;Data Structure&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use consistent schemas&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;While FlutterDB is schemaless, consistent document structures improve code maintainability&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Choose good document IDs&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Let FlutterDB generate IDs unless you have specific requirements&lt;/li&gt;
&lt;li&gt;Custom IDs should be unique and consistent&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handle relationships thoughtfully&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For one-to-many relationships, consider embedding related data or using references&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;error-handling&quot;&gt;Error Handling&lt;/h4&gt;
&lt;p&gt;Always implement error handling:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;try {
  await users.insert({'name': 'John'});
} catch (e) {
  print('Error inserting document: $e');
  // Handle error appropriately
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;limitations&quot;&gt;Limitations&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Not a full MongoDB replacement&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Limited subset of MongoDB query operators&lt;/li&gt;
&lt;li&gt;Some complex aggregation operations not supported&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance with large datasets&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Best suited for mobile apps with moderate data size&lt;/li&gt;
&lt;li&gt;Consider pagination or limiting queries for large collections&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No built-in encryption&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data is stored in plain text&lt;/li&gt;
&lt;li&gt;Consider additional encryption for sensitive data&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Limited indexing options&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Custom indexes not yet supported&lt;/li&gt;
&lt;li&gt;Consider query performance for large collections&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No network synchronization&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local database only&lt;/li&gt;
&lt;li&gt;Implement your own sync solution if needed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;complete-example&quot;&gt;Complete Example&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;import 'package:flutter/material.dart';
import 'package:flutterdb/flutterdb.dart';

class UserRepository {
  final FlutterDB _db = FlutterDB();
  late Future&amp;lt;Collection&amp;gt; _users;

  UserRepository() {
    _users = _db.collection('users');
  }

  Future&amp;lt;String&amp;gt; addUser(String name, String email, int age) async {
    final collection = await _users;
    return await collection.insert({
      'name': name,
      'email': email,
      'age': age,
      'createdAt': DateTime.now().toIso8601String(),
    });
  }

  Future&amp;lt;List&amp;lt;Map&amp;lt;String, dynamic&amp;gt;&amp;gt;&amp;gt; getAdultUsers() async {
    final collection = await _users;
    return await collection.find({
      'age': {'$gte': 18},
    });
  }

  Future&amp;lt;void&amp;gt; updateUserStatus(String id, String status) async {
    final collection = await _users;
    await collection.updateById(id, {
      'status': status,
      'updatedAt': DateTime.now().toIso8601String(),
    });
  }
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  final userRepo = UserRepository();
  
  // Add a user
  final userId = await userRepo.addUser('Alice', '*Emails are not allowed*', 28);
  print('Added user with ID: $userId');
  
  // Update user status
  await userRepo.updateUserStatus(userId, 'premium');
  
  // Get all adult users
  final adults = await userRepo.getAdultUsers();
  for (var user in adults) {
    print('User: ${user['name']}, Age: ${user['age']}, Status: ${user['status']}');
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;future-enhancements&quot;&gt;Future Enhancements&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Custom indexing support&lt;/li&gt;
&lt;li&gt;More aggregation pipeline operators&lt;/li&gt;
&lt;li&gt;Full-text search capabilities&lt;/li&gt;
&lt;li&gt;Data encryption options&lt;/li&gt;
&lt;li&gt;Schema validation&lt;/li&gt;
&lt;li&gt;Observable queries&lt;/li&gt;
&lt;li&gt;Migration support&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;contributing&quot;&gt;Contributing&lt;/h3&gt;
&lt;p&gt;Contributions are welcome! Please feel free to submit a Pull Request.&lt;/p&gt;
&lt;h3 id=&quot;license&quot;&gt;License&lt;/h3&gt;
&lt;p&gt;This project is licensed under the GNU AGPLv3 License - see the LICENSE file for details.&lt;/p&gt;
&lt;p&gt;If you are searching for this keywords &lt;br&gt;
&quot;offline-first Flutter apps&quot;&lt;br&gt;
&quot;local storage in Flutter&quot;&lt;br&gt;
&quot;best local database Flutter&quot;&lt;/p&gt;
&lt;p&gt;your search ends here &lt;br&gt;
I'd love your thoughts. Try &lt;a rel=&quot;nofollow&quot; href=&quot;https://pub.dev/packages/flutterdb&quot;&gt;FlutterDB&lt;/a&gt; in your next project and let me know how it goes! You can ⭐️ the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/KTBsomen/flutterdb&quot;&gt;GitHub repo&lt;/a&gt; or open an issue if you hit any snags. &lt;/p&gt;
</description>
<category>Open Source</category>
<guid isPermaLink="true">https://coderlegion.com/2868/local-mongodb-like-database-in-flutter</guid>
<pubDate>Mon, 05 May 2025 17:07:35 +0000</pubDate>
</item>
<item>
<title>Build a Full-Stack Video Streaming App with React.js, Node.js, Next.js, MongoDB, Bunny CDN, and Material UI</title>
<link>https://coderlegion.com/1697/build-full-stack-video-streaming-app-with-react-node-js-next-mongodb-bunny-cdn-and-material</link>
<description>&lt;p&gt;The rise of video streaming platforms like YouTube, Netflix, and Twitch has transformed how we consume media. But what if you could build your own? In this comprehensive guide, we’ll walk you through creating a full-stack video streaming application using a modern tech stack: React.js, Node.js, Next.js, MongoDB, Bunny CDN, and Material UI (MUI). By the end, you’ll have a feature-rich app that supports video uploads, streaming, user interaction, and more—perfect for personal projects, learning, or even launching your own platform. Let’s dive into the process!&lt;/p&gt;
&lt;p&gt;In this tutorial, we’ll create a powerful, full-stack video streaming application using React.js for dynamic, reusable components, Node.js for a reliable and scalable backend, Next.js for seamless server-side rendering and routing, MongoDB for efficient and flexible data management, Bunny CDN for lightning-fast video delivery, and Material UI (MUI) for a sleek, professional user interface. By the end of this journey, you’ll have a fully functional streaming app packed with modern, cutting-edge features—ready to rival platforms like YouTube or Netflix. Let’s get started!&lt;/p&gt;
&lt;p&gt;Key Features You’ll Build:&lt;/p&gt;
&lt;p&gt;✅ HLS Video Streaming: Deliver smooth, adaptive video playback across all devices.&lt;/p&gt;
&lt;p&gt;✅ Debounced Search Functionality: Enhance search performance with efficient, user-friendly input handling.&lt;/p&gt;
&lt;p&gt;✅ Data Fetching &amp;amp; Pagination with SWR: Seamlessly fetch video data with infinite scrolling and caching.&lt;/p&gt;
&lt;p&gt;✅ Notification Display: Keep users engaged with real-time alerts and updates.&lt;/p&gt;
&lt;p&gt;✅ MUI Toolpad Dashboard Design: Build a robust admin dashboard using Material UI’s advanced tools.&lt;/p&gt;
&lt;p&gt;✅ Bookmark: Allow users to save their favorite videos effortlessly.&lt;/p&gt;
&lt;p&gt;✅ Like &amp;amp; Unlike: Add interactive like/unlike features to boost engagement.&lt;/p&gt;
&lt;p&gt;✅ Save Video History &amp;amp; Last Watched Time: Track user viewing progress and history with ease.&lt;/p&gt;
&lt;p&gt;✅ Comment: Enable users to leave comments and interact with content.&lt;/p&gt;
&lt;p&gt;✅ MongoDB Modeling: Design an optimized database schema for videos, users, and more.&lt;/p&gt;
&lt;p&gt;✅ Upload &amp;amp; Publish Videos in Different Resolutions: Process and store videos in multiple quality levels.&lt;/p&gt;
&lt;p&gt;✅ Securely Download Videos: Provide safe, authenticated video downloads for offline viewing.&lt;/p&gt;
&lt;p&gt;✅ Share Videos to Different Platforms: Integrate social sharing for platforms like Twitter and Facebook.&lt;/p&gt;
&lt;p&gt;✅ Implement PWA: Transform your app into a Progressive Web App for a native-like experience.&lt;/p&gt;
&lt;p&gt;✅ SEO-Friendly: Optimize for search engines using Next.js SSR and metadata.&lt;/p&gt;
&lt;p&gt;✅ Check Video Statistics: Display analytics like views, likes, and watch time.&lt;/p&gt;
&lt;p&gt;✅ Stream in Different Resolutions &amp;amp; Quality: Let users select their preferred playback quality.&lt;/p&gt;
&lt;p&gt;✅ Check Trending &amp;amp; Subscribed Channels: Showcase trending content and user subscriptions.&lt;/p&gt;
&lt;h4 id=&quot;why-this-tech-stack-&quot;&gt;Why This Tech Stack?&lt;/h4&gt;
&lt;p&gt;Before we start, let’s break down why this combination of tools is ideal for a video streaming app:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;React.js:&lt;/strong&gt; Powers dynamic, reusable UI components for a responsive and interactive frontend.&lt;br&gt;
&lt;strong&gt;Next.js:&lt;/strong&gt; Enhances the app with server-side rendering (SSR), static site generation, and built-in routing for performance and SEO.&lt;br&gt;
&lt;strong&gt;MongoDB:&lt;/strong&gt; Offers a flexible, NoSQL database for storing video metadata, user profiles, and interactions.&lt;br&gt;
&lt;strong&gt;Bunny CDN:&lt;/strong&gt; Ensures ultra-fast video delivery with global content distribution and HLS streaming support.&lt;br&gt;
&lt;strong&gt;Material UI (MUI):&lt;/strong&gt; Delivers a polished, professional design system and tools like MUI Toolpad for rapid UI development.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Full Video Implementation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/1cvaPMDUXVQ?si=GZpm1mzW-8_sjajF&quot;&gt;A video is worth a thousand words. Click here to watch&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;By the end of this project, you’ll have a full-stack video streaming app that’s not just functional but packed with modern features like HLS streaming &amp;amp; PWA support. Whether you’re building for fun, learning, or launching a startup, this app is a solid foundation. Experiment with additional features like live streaming or analytics to take it even further. Happy coding, and enjoy your YouTube-inspired creation!&lt;/p&gt;
</description>
<category>Getting Started</category>
<guid isPermaLink="true">https://coderlegion.com/1697/build-full-stack-video-streaming-app-with-react-node-js-next-mongodb-bunny-cdn-and-material</guid>
<pubDate>Sat, 29 Mar 2025 09:22:42 +0000</pubDate>
</item>
<item>
<title>4 ways to query GeoJSON data in MongoDB</title>
<link>https://coderlegion.com/1672/4-ways-to-query-geojson-data-in-mongodb</link>
<description>&lt;p&gt;Have you ever heard of GeoJSON? If not, let me explain it to you.&lt;br&gt;
GeoJSON is a format for encoding a variety of geographic data structures created and published in 2008.&lt;/p&gt;
&lt;p&gt;Since 2015, it has an official &lt;a rel=&quot;nofollow&quot; href=&quot;https://tools.ietf.org/html/rfc7946&quot;&gt;specification&lt;/a&gt; created by its original authors with the collaboration of the Internet Engineering Task Force &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.ietf.org/&quot;&gt;(IETF)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;According to this specification:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;GeoJSON is a geospatial data interchange format based on JavaScript Object Notation (JSON).  It defines several types of JSON objects and the manner in which they are combined to represent data about geographic features, their properties, and their spatial extents. GeoJSON uses a geographic coordinate reference system, World Geodetic System 1984, and units of decimal degrees.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is a format widely used across JSON-based applications to read, manipulate and compare geospatial data.&lt;/p&gt;
&lt;p&gt;Several third-party map libraries provide GeoJSON support for front-end applications, some of them are:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://leafletjs.com/reference-1.7.1.html#geojson&quot;&gt;Leaflet&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://openlayers.org/en/latest/examples/geojson.html&quot;&gt;Openlayers&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.google.com/maps/documentation/javascript/datalayer#load_geojson&quot;&gt;Google Maps&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Considering that now you know a little bit more about GeoJSON, let´s move to the manipulation bit.&lt;br&gt;
When building maps for a web application, it is common to deal with something we call &quot;Layers&quot;. &lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.google.com/maps/documentation/javascript/layers&quot;&gt;According to Google&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Layers are objects on the map that consist of one or more separate items but are manipulated as a single unit. Layers generally reflect collections of objects that you add on top of the map to designate a common association.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A Layer can have different formats and one of them is GeoJSON.&lt;/p&gt;
&lt;p&gt;So, when working with more than one layer in a map, it is common to perform operations between them to determine their geospatial relationship within a context. By making use of geospatial operations, it is possible to determine, for example, if:&lt;/p&gt;
&lt;p&gt;i) a Point lies within a Polygon;&lt;br&gt;
ii) a Polygon intersects another Polygon;&lt;br&gt;
iii) a Line crosses a circle;&lt;br&gt;
iv) a Point is part of a Line;&lt;/p&gt;
&lt;p&gt;But, depending on the size of the involved layers, manipulating or comparing them directly with JavaScript, in the browser, might cause performance issues since the memory usage tends to increase even more if they need to be rendered on a map.&lt;/p&gt;
&lt;p&gt;A good solution for this case is to perform any required geospatial operation directly in the database. Most of the database engines provide functions to query GeoJSON and one of them is &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.mongodb.com/manual/reference/geojson/&quot;&gt;MongoDB&lt;/a&gt; which, by the way, has been pretty optimized for spatial operations since &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.mongodb.com/blog/post/geospatial-performance-improvements-in-mongodb-3-2&quot;&gt;version 3.2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These are some of the most basic geospatial structures that MongoDB supports:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Point&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;type&quot;: &quot;Point&quot;,
  &quot;coordinates&quot;: [
    -42.53700256347656,
    -22.28496664336935
   ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Linestring&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;type&quot;: &quot;LineString&quot;,
  &quot;coordinates&quot;: [
    [
      -43.17626953125,
      -22.91792293614603
    ],
    [
      -40.2978515625,
      -20.262197124246534
    ]
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Polygon&lt;/strong&gt; &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;type&quot;: &quot;Polygon&quot;,
  &quot;coordinates&quot;: [
    [
      [
        -42.94006347656249,
        -22.649502094242195
      ],
      [
        -42.16552734375,
        -22.649502094242195
      ],
      [
        -42.16552734375,
        -21.968519331082298
      ],
      [
        -42.94006347656249,
        -21.968519331082298
      ],
      [
        -42.94006347656249,
        -22.649502094242195
      ]
    ]
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Geometry Collection&lt;/strong&gt; &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  type: &quot;GeometryCollection&quot;,
  geometries: [
    {
      &quot;type&quot;: &quot;Point&quot;,
      &quot;coordinates&quot;: [
        -42.53700256347656,
        -22.28496664336935
      ]
    },
    {
      &quot;type&quot;: &quot;LineString&quot;,
      &quot;coordinates&quot;: [
        [
          -43.17626953125,
          -22.91792293614603
        ],
        [
          -40.2978515625,
          -20.262197124246534
        ]
      ]
    },
    {
      &quot;type&quot;: &quot;Polygon&quot;,
      &quot;coordinates&quot;: [
        [
          [
            -42.94006347656249,
            -22.649502094242195
          ],
          [
            -42.16552734375,
            -22.649502094242195
          ],
          [
            -42.16552734375,
            -21.968519331082298
          ],
          [
            -42.94006347656249,
            -21.968519331082298
          ],
          [
            -42.94006347656249,
            -22.649502094242195
          ]
        ]
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://geojson.io/#id=gist:vcpablo/2758df2cb48a5b1031b77a18c60cea9d&amp;amp;map=8/-21.596/-41.737&quot;&gt;Here&lt;/a&gt; you can find the mentioned examples plotted in a map using geojson.io.&lt;/p&gt;
&lt;p&gt;Now, I'm going to show you some basic operations that can be performed directly in a Mongo database assuming that there is already a collection named &lt;code&gt;geospatial&lt;/code&gt; with the &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.mongodb.com/manual/core/2dsphere/&quot;&gt;proper indexes&lt;/a&gt; set up.&lt;/p&gt;
&lt;p&gt;To ease the query implementation, I recommend setting the GeoJSON object inside a property of your document. In the following example, the property &quot;location&quot; represents the geometry: &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;$id&quot;: &quot;342j3ug4kj23b4k234&quot;
  &quot;location&quot;: {
    &quot;type&quot;: &quot;Point&quot;,
    &quot;coordinates&quot;: [ -22, -42 ]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;1-finding-geometries-that-intersect-with-the-given-point&quot;&gt;1. Finding geometries that intersect with the given Point&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;db.geospatial.find({
  location: {
    $geoIntersects: {
     $geometry: {
       type: &quot;Point&quot;,
       coordinates: [
         -42.53700256347656,
         -22.28496664336935
        ]
      }
    }
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This query will retrieve all geometries with which the given Point intersects. This example shows a query passing a Point as a parameter, but it supports any &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.mongodb.com/manual/reference/geojson/&quot;&gt;GeoJSON Object&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-finding-all-geometries-that-lie-within-a-given-geometry&quot;&gt;2. Finding all geometries that lie within a given geometry&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;db.geospatial.find({
  location: {
    $geoWithin: {
      $geometry: {
        &quot;type&quot;: &quot;Polygon&quot;,
        &quot;coordinates&quot;: [
          [
            [
              -42.94006347656249,
              -22.649502094242195
            ],
            [
              -42.16552734375,
              -22.649502094242195
            ],
            [
              -42.16552734375,
              -21.968519331082298
            ],
            [
              -42.94006347656249,
              -21.968519331082298
            ],
            [
              -42.94006347656249,
              -22.649502094242195
            ]
          ]
        ]
      }
  }
    }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This query will retrieve all geometries that are inside the given Polygon and it is useful to look for all points that are placed within an area. For example: let's say you'd like to find all restaurants in a given city.&lt;/p&gt;
&lt;h3 id=&quot;3-finding-geometries-within-a-number-of-miles-from-a-given-point&quot;&gt;3. Finding geometries within a number of miles from a given point&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;db.geospatial.find({
  location: {
    $geoWithin: {
      $centerSphere: [
        [ -42.53700256347656, -22.28496664336935 ],
        5 / 3963.2
      ]
    }
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;$centerSphere&lt;/code&gt;, MongoDB will &quot;draw&quot; a circle with a pre-defined size from a given point. In this case, the query will retrieve all geometries that are up to 5 miles to the coordinates provided.&lt;br&gt;
It is important to mention that, in this case, the geometries will be retrieved unordered.&lt;/p&gt;
&lt;h3 id=&quot;4-finding-ordered-geometries-within-a-number-of-miles-from-a-given-point&quot;&gt;4. Finding &lt;em&gt;ordered&lt;/em&gt; geometries within a number of miles from a given point&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const METERS_PER_MILE = 1609.34

db.geospatial.find({
  location: {
    $nearSphere: {
      $geometry: {
        type: &quot;Point&quot;,
        coordinates: [ -73.93414657, 40.82302903 ]
      },
      $maxDistance: 5 * 1609.34
    }
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This query will retrieve the same result as the previous one, but order from the nearest to the farthest.&lt;br&gt;
Check &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.mongodb.com/manual/tutorial/calculate-distances-using-spherical-geometry-with-2d-geospatial-indexes/&quot;&gt;this page&lt;/a&gt; for more information on distance unit conversion.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;GeoJSON is the most used format to represent Geospatial data in web applications. It is fully supported by the most popular third-party map rendering libraries and database engines.&lt;/p&gt;
&lt;p&gt;This article demonstrated some basic concepts and how to implement some of the spatial operations provided by MongoDB.&lt;/p&gt;
&lt;p&gt;I hope you've liked it.&lt;br&gt;
Please, share and comment... any feedback is welcome!&lt;/p&gt;
</description>
<category>Queries</category>
<guid isPermaLink="true">https://coderlegion.com/1672/4-ways-to-query-geojson-data-in-mongodb</guid>
<pubDate>Tue, 25 Mar 2025 23:51:49 +0000</pubDate>
</item>
<item>
<title>Best Databases to Use with the MERN Stack Apart from MongoDB</title>
<link>https://coderlegion.com/1433/best-databases-to-use-with-the-mern-stack-apart-from-mongodb</link>
<description>&lt;p&gt;The &lt;strong&gt;MERN stack&lt;/strong&gt; (MongoDB, Express, React, Node.js) has become one of the most popular choices for modern web development due to its flexibility, scalability, and the ease with which the technologies work together. MongoDB is the default database for many MERN stack developers, primarily because it is a NoSQL database and integrates seamlessly with Node.js. However, MongoDB isn’t the only option available for building a robust, scalable backend. Depending on your project’s requirements, you might prefer to use a different database.&lt;/p&gt;
&lt;p&gt;In this article, we will explore the best databases to use with the MERN stack, apart from MongoDB. Each database has its unique strengths and can be an excellent choice for specific use cases, from relational data management to high scalability and real-time applications. Whether you're building a large-scale enterprise application or a small personal project, there’s a database that will suit your needs.&lt;/p&gt;
&lt;h3 id=&quot;1-postgresql-a-powerful-relational-database&quot;&gt;1. PostgreSQL: A Powerful Relational Database&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; is an open-source, object-relational database system that is widely regarded for its stability, extensibility, and support for advanced SQL features. If your application requires complex queries, a high level of data integrity, or sophisticated transaction handling, PostgreSQL is an excellent choice. It is fully ACID-compliant, which ensures that transactions are processed reliably, even in the event of a system crash.&lt;/p&gt;
&lt;p&gt;PostgreSQL is not only known for its SQL features but also its ability to handle JSON data types through its JSONB feature. This gives developers the flexibility to combine the structure of SQL with the unstructured nature of NoSQL, making it ideal for hybrid applications. If your project involves extensive data relationships, PostgreSQL's support for foreign keys, joins, and transactions ensures a solid foundation for building complex, data-driven applications.&lt;/p&gt;
&lt;p&gt;When integrating PostgreSQL with a MERN stack, you can use Node.js libraries such as &lt;strong&gt;pg&lt;/strong&gt; or ORM frameworks like &lt;strong&gt;Sequelize&lt;/strong&gt; or &lt;strong&gt;TypeORM&lt;/strong&gt; for a more structured approach. These tools allow you to interact with PostgreSQL using JavaScript, making it easy to manage the database and implement queries directly within your Node.js backend.&lt;/p&gt;
&lt;h3 id=&quot;2-mysql-a-highly-efficient-relational-database&quot;&gt;2. MySQL: A Highly Efficient Relational Database&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;MySQL&lt;/strong&gt; is another widely used relational database that has been a staple in the development world for decades. Known for its speed and reliability, MySQL is a strong contender for any application that requires structured data storage and SQL querying. It is particularly useful for applications that handle a large number of read and write operations, such as e-commerce platforms, content management systems, and business applications.&lt;/p&gt;
&lt;p&gt;One of MySQL's standout features is its performance, especially when it comes to handling read-heavy applications. For projects that involve a large number of concurrent users or need fast access to data, MySQL provides excellent scalability options through indexing and query optimization techniques.&lt;/p&gt;
&lt;p&gt;MySQL also supports replication, which means you can set up master-slave configurations for load balancing and increased reliability. This makes it an excellent choice for building high-traffic applications. If your project relies on relational data and complex queries, MySQL is a solid option for your MERN stack backend.&lt;/p&gt;
&lt;p&gt;Integrating MySQL with MERN can be done using libraries like &lt;strong&gt;mysql2&lt;/strong&gt; or using ORM tools like &lt;strong&gt;Sequelize&lt;/strong&gt; or &lt;strong&gt;TypeORM&lt;/strong&gt;, which allow developers to interact with MySQL using JavaScript and simplify the management of data relationships.&lt;/p&gt;
&lt;h3 id=&quot;3-sqlite-lightweight-and-serverless&quot;&gt;3. SQLite: Lightweight and Serverless&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;SQLite&lt;/strong&gt; is a lightweight, file-based database that doesn’t require a dedicated server. It’s an excellent choice for small-scale applications, mobile apps, or for use in development and testing environments. While SQLite isn’t suitable for applications that need to scale horizontally or handle heavy concurrent traffic, it is ideal for situations where simplicity and ease of use are paramount.&lt;/p&gt;
&lt;p&gt;SQLite is embedded directly into the application, making it easy to set up and use without the overhead of managing a separate database server. It is commonly used in mobile applications or small web apps that don’t require a full-fledged database management system.&lt;/p&gt;
&lt;p&gt;When working with SQLite in the MERN stack, you can use the &lt;strong&gt;sqlite3&lt;/strong&gt; package in Node.js, or you can opt for an ORM like &lt;strong&gt;Sequelize&lt;/strong&gt;, which allows you to define models and perform database operations seamlessly within your application.&lt;/p&gt;
&lt;h3 id=&quot;4-cassandra-a-nosql-database-for-high-scalability&quot;&gt;4. Cassandra: A NoSQL Database for High Scalability&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Cassandra&lt;/strong&gt; is a distributed NoSQL database designed to handle huge amounts of data across many servers. It is built to provide high availability, scalability, and fault tolerance, making it ideal for applications that need to handle large volumes of data and ensure uptime even during network failures or server outages. If your application requires a highly scalable, fault-tolerant solution, Cassandra might be the right choice.&lt;/p&gt;
&lt;p&gt;Unlike MongoDB, which stores documents, Cassandra uses a column-family store to organize data. This makes it particularly well-suited for time-series data, real-time analytics, or applications that require fast writes and large data sets. Cassandra is used by companies like Netflix, Instagram, and Spotify for managing their massive datasets at scale.&lt;/p&gt;
&lt;p&gt;Integrating Cassandra with your MERN stack can be done using the &lt;strong&gt;cassandra-driver&lt;/strong&gt; for Node.js. This driver allows you to connect to Cassandra, perform queries, and manage data within your backend.&lt;/p&gt;
&lt;h3 id=&quot;5-redis-a-high-performance-in-memory-store&quot;&gt;5. Redis: A High-Performance In-Memory Store&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Redis&lt;/strong&gt; is an in-memory data store that can be used as a cache, session store, or for real-time data processing. While Redis isn’t typically used as a primary database for storing persistent data, it’s highly effective for scenarios where low-latency access to data is critical. Redis excels at handling high-throughput workloads and can store a wide range of data types, including strings, hashes, lists, sets, and sorted sets.&lt;/p&gt;
&lt;p&gt;Redis is commonly used in applications that require fast data retrieval, such as caching frequently accessed data, managing user sessions, or handling real-time data for messaging or notification systems. It can also be used to store application state or for pub/sub messaging between services.&lt;/p&gt;
&lt;p&gt;To integrate Redis with MERN, you can use the &lt;strong&gt;ioredis&lt;/strong&gt; or &lt;strong&gt;redis&lt;/strong&gt; Node.js clients. These libraries make it easy to connect Redis to your Node.js backend and implement caching, sessions, and real-time features.&lt;/p&gt;
&lt;h3 id=&quot;6-firebase-firestore-real-time-nosql-database&quot;&gt;6. Firebase Firestore: Real-Time NoSQL Database&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Firebase Firestore&lt;/strong&gt; is a cloud-based NoSQL database developed by Google. It provides real-time data synchronization across multiple devices and offline data persistence, which makes it ideal for mobile apps or applications that require real-time updates. Firestore is part of the broader Firebase ecosystem, which includes authentication, hosting, and cloud functions, making it a powerful tool for building full-stack applications.&lt;/p&gt;
&lt;p&gt;Firestore is particularly useful for applications that require automatic syncing of data across devices, such as collaborative apps, chat applications, or apps with live updates. The real-time features in Firestore allow for seamless user experiences, where data updates reflect instantly without needing to refresh the app.&lt;/p&gt;
&lt;p&gt;Integration with MERN is straightforward, as Firebase provides an official SDK that can be easily used in your Node.js backend. Firebase also offers SDKs for React, which makes it simple to integrate Firestore into the front end of your MERN stack application.&lt;/p&gt;
&lt;h3 id=&quot;7-couchdb-a-document-based-nosql-database&quot;&gt;7. CouchDB: A Document-Based NoSQL Database&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;CouchDB&lt;/strong&gt; is another document-based NoSQL database that uses JSON for data storage. CouchDB is designed with replication and synchronization in mind, making it an excellent choice for apps that need offline capabilities or peer-to-peer data syncing. It is known for its simplicity, reliability, and ease of use. CouchDB supports MapReduce for views, which can be used to aggregate data.&lt;/p&gt;
&lt;p&gt;Similar to MongoDB, CouchDB stores JSON documents but offers a unique set of features for working with distributed data. If your application needs easy replication across multiple nodes or offline data access, CouchDB might be an ideal fit.&lt;/p&gt;
&lt;p&gt;To integrate CouchDB into your MERN stack, you can use libraries like &lt;strong&gt;nano&lt;/strong&gt; or &lt;strong&gt;pouchdb&lt;/strong&gt;, which make it easy to interact with the database from your Node.js application.&lt;/p&gt;
&lt;h3 id=&quot;8-neo4j-a-graph-database-for-connected-data&quot;&gt;8. Neo4j: A Graph Database for Connected Data&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Neo4j&lt;/strong&gt; is a graph database that is specifically designed to store and query highly connected data. It’s an ideal choice for applications that need to work with complex relationships, such as social networks, recommendation engines, and fraud detection systems. Instead of using rows and columns, Neo4j stores data as nodes and edges, which are linked to each other through relationships.&lt;/p&gt;
&lt;p&gt;If your MERN stack application requires handling of highly interconnected data, such as modeling relationships between users, products, and transactions, Neo4j is a powerful solution. Neo4j’s Cypher query language allows you to query graph data intuitively and efficiently, making it a great choice for applications that need to explore complex relationships between entities.&lt;/p&gt;
&lt;p&gt;Neo4j can be integrated with MERN using the official &lt;strong&gt;neo4j-driver&lt;/strong&gt; for Node.js, which allows you to query the graph database directly from your backend and incorporate it into your application.&lt;/p&gt;
</description>
<category>Web Dev</category>
<guid isPermaLink="true">https://coderlegion.com/1433/best-databases-to-use-with-the-mern-stack-apart-from-mongodb</guid>
<pubDate>Tue, 11 Mar 2025 03:19:58 +0000</pubDate>
</item>
<item>
<title>MongoDB: A Beginner's Guide to NoSQL Databases</title>
<link>https://coderlegion.com/1026/mongodb-a-beginners-guide-to-nosql-databases</link>
<description>&lt;h2 id=&quot;table-of-contents-&quot;&gt;Table of contents:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Introduction&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What is MongoDB&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What MongoDB is not&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Why you should learn MongoDB&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Advantages of using MongoDB&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mongodb drawbacks&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When to use MongoDB&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When not to use MongoDB&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Benefits of MongoDB&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Challenges&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Conclusion&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Glossary&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have you ever wondered how large-scale applications like social media sites, online stores, and mobile apps manage to store vast amounts of data efficiently?&lt;/p&gt;
&lt;p&gt;In today's world of fast-moving data, databases like MongoDB have become increasingly popular. This article will introduce you to MongoDB, a powerful NoSQL database that's widely used for storing unstructured data.&lt;/p&gt;
&lt;p&gt;By the end of this guide, you'll understand what MongoDB is, why it's popular, how it compares to SQL databases, and when to use it (and when not to).&lt;/p&gt;
&lt;p&gt;To begin understanding MongoDB, let's first explore what a database is in general.&lt;/p&gt;
&lt;p&gt;A database is a structured collection of data that allows for easy access, management, and update. There are two main types of databases:&lt;/p&gt;
&lt;h3 id=&quot;sql-structured-query-language-databases-&quot;&gt;&lt;strong&gt;SQL (Structured Query Language) databases:&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt; These are traditional relational databases (like MySQL, PostgreSQL, etc.) that store data in tables with rows and columns.&lt;/p&gt;
&lt;h3 id=&quot;nosql-databases-&quot;&gt;&lt;strong&gt;NoSQL databases:&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;These are designed for unstructured or semi-structured data. MongoDB is one of the most popular NoSQL databases, which means it handles data differently than SQL databases.&lt;/p&gt;
&lt;h2 id=&quot;what-is-mongodb-&quot;&gt;What is MongoDB?&lt;/h2&gt;
&lt;p&gt;MongoDB is a NoSQL, document-based database that stores data in a JSON-like format called BSON (Binary JSON). Unlike SQL databases, MongoDB does not require a fixed schema. This flexibility makes it great for applications that need to scale quickly and manage different types of data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Imagine a social media app storing user information. Each user may have different attributes: some may have phone numbers, others might not. MongoDB allows you to store this data without needing to define a rigid structure, unlike traditional SQL databases.&lt;/p&gt;
&lt;h4 id=&quot;what-mongodb-is-not&quot;&gt;What MongoDB is Not&lt;/h4&gt;
&lt;p&gt;MongoDB is not a relational database.&lt;/p&gt;
&lt;p&gt;While SQL databases work best for applications that require strict relationships between data (like financial systems or traditional enterprise applications), MongoDB is best for handling unstructured data or applications that require quick scalability, like real-time analytics, content management systems, and IoT platforms.&lt;/p&gt;
&lt;h2 id=&quot;why-you-should-learn-mongodb&quot;&gt;Why You Should Learn MongoDB&lt;/h2&gt;
&lt;p&gt;As the demand for modern, data-driven applications continues to grow, MongoDB is becoming more relevant. Here's why learning MongoDB in 2025 is a good idea:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Growing Demand: More companies are adopting NoSQL databases for their flexibility and scalability.&lt;br&gt;
Ease of Use: MongoDB's document-based structure is easier to work with than SQL's table-based structure, making it ideal for beginners.&lt;br&gt;
Big Data and Cloud-Friendly: MongoDB is widely used for managing big data and working in cloud environments.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;mongodb-vs-sql&quot;&gt;MongoDB vs SQL&lt;/h2&gt;
&lt;p&gt;One of the most common comparisons is between MongoDB and traditional SQL databases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-   Schema:&lt;/strong&gt; SQL requires a fixed schema (tables, rows, columns), while MongoDB's schema-less design allows you to store different types of data without predefined structures.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-   Scaling:&lt;/strong&gt; MongoDB is built to scale horizontally (across multiple servers), while SQL databases often scale vertically (increasing server power).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-   Data Storage:&lt;/strong&gt; SQL uses tables to store data, while MongoDB stores data in collections of documents, which are more flexible.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXdabvMv1ETYXBq7x-wieQrpaT2qgRutFGNspTM3vjYdN8V9zzfizLPL_OoNMkYIVFOi2Sav9sdkKghAWiD7fbeXmn5dwYEneq9eM46V31J7DOue9r2VaQ2K8k22eB2qyoIeRD4aeQ?key=uSWbuKqfVoQzHDkZhAd9_C0R&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;advantages-of-using-mongodb&quot;&gt;Advantages of Using MongoDB&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Scalability: MongoDB can handle large amounts of data, scaling easily as your application grows.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flexibility: It allows for storing various data types, from simple records to complex documents with nested arrays and objects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;High Performance: MongoDB offers fast data retrieval due to its document-based structure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Real-time Analytics: MongoDB is ideal for applications that require real-time processing, like analytics platforms.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;mongodb-drawbacks&quot;&gt;MongoDB Drawbacks&lt;/h2&gt;
&lt;p&gt;While MongoDB has many advantages, it's important to note a few drawbacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Lack of Complex Transactions: While MongoDB supports basic transactions, it doesn't handle complex multi-table transactions as efficiently as SQL databases.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consistency: In certain use cases, MongoDB might sacrifice strict data consistency (ACID properties) for performance and scalability. This could be a concern for systems that require precise consistency, like banking systems.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;when-to-use-mongodb&quot;&gt;When to Use MongoDB&lt;/h2&gt;
&lt;p&gt;You should consider using MongoDB when:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;You are building a project that requires scalability and flexibility, such as social networks, content management systems, or real-time analytics platforms.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your application deals with unstructured or semi-structured data (e.g., logs, user-generated content, etc.).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You need fast development cycles and agile methodologies where schema design might change often.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;when-not-to-use-mongodb&quot;&gt;When Not to Use MongoDB&lt;/h2&gt;
&lt;p&gt;MongoDB is not ideal when:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Your project requires complex transactions (e.g., banking applications or systems requiring multi-step business logic).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your data needs to follow strict relationships and constraints (e.g., inventory systems, financial applications, or CRM systems).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consistency is more important than availability or partition tolerance (known as the CAP theorem).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;benefits-of-mongodb-&quot;&gt;Benefits of MongoDB:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Schema Flexibility: Ideal for applications that handle varying types of data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Horizontal Scalability: You can scale your app seamlessly with sharding.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;High Availability: MongoDB supports replication for fault tolerance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Faster Development: You can iterate faster with MongoDB's flexible data structure.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Despite the benefits, MongoDB may not be suitable for all use cases.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Learning Curve: Although MongoDB is beginner-friendly, getting familiar with its advanced features (like sharding and replication) can take time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data Integrity: MongoDB does not enforce data integrity as strictly as relational databases, so it's important to plan for handling data consistency issues in your application.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;section&quot;&gt;&lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXe6bSgFmShiACmFIz0Fm4TtcKXblAUT6X_2x9G2evdDxnK_gyEsPonL1hKZ51d6MvJv9dcFwp18ikkttQw4_Zeo4PDq7GJQLS_ETU4uJ0vZWbTquVa8iQ7mZtvZswyl1nmurN3e?key=uSWbuKqfVoQzHDkZhAd9_C0R&quot; alt=&quot;&quot;&gt;&lt;/h4&gt;
&lt;h2 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;MongoDB is an excellent choice for modern applications that need scalability, flexibility, and fast development. While it's not suitable for all use cases, especially those requiring complex transactions or rigid data structures, it is a powerful tool for developers working on dynamic applications. By understanding the strengths and weaknesses of MongoDB, you can make an informed decision about whether it's the right tool for your project.&lt;/p&gt;
&lt;p&gt;Have you worked with MongoDB before? What was your experience? Share your thoughts in the comments below, or feel free to ask questions. For more beginner tutorials, check out &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/anadudev&quot;&gt;my related articles&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Glossary&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;NoSQL: A type of database designed to handle unstructured or semi-structured data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;BSON: A binary format of JSON used by MongoDB for storing documents.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sharding: A method of distributing data across multiple servers to ensure scalability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Replication: The process of copying data across multiple servers to ensure high availability.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Database</category>
<guid isPermaLink="true">https://coderlegion.com/1026/mongodb-a-beginners-guide-to-nosql-databases</guid>
<pubDate>Sun, 02 Feb 2025 09:56:37 +0000</pubDate>
</item>
</channel>
</rss>