Use Cases (Updated: 6/1/2026)

Claude Code NoSQL/MongoDB Guide: Schema, Indexes, Aggregation

Design MongoDB with Claude Code from access patterns to validation, indexes, aggregation, tests, and rollout.

Claude Code NoSQL/MongoDB Guide: Schema, Indexes, Aggregation

Start with Access Patterns, Not Collections

NoSQL/MongoDB means a database that does not force every record into fixed rows and columns. MongoDB stores data as JSON-like documents, so the right model depends on how the application reads, writes, and aggregates those documents.

Claude Code is most useful when you give it the shape of the workflow before asking for code. Do not start with “create users, products, and orders collections.” Start with the screens, API responses, update frequency, expected data volume, and reporting needs. That context lets Claude Code reason about embedding versus references, indexes, aggregation pipelines, validation schema, seed data, and rollout risk.

Three Practical Use Cases

Use caseCommon readsGood MongoDB shapeMain risk
E-commerce ordersrecent orders by user, order detail, revenue by monthembed purchased item name and price, keep productId referencerewriting order history when product data changes
SaaS audit logsorganization, user, date range, event typeappend-only documents with compound indexes and possible TTLslow scans on large log collections
CMS articleslookup by slug, list by status, category archivesexplicit slug, status, and date indexesexposing drafts or internal notes
Support ticketscustomer queue, assigned agent, recent commentsembed bounded recent comments, reference large attachmentsunbounded arrays and document growth

For related design context, see database design with Claude Code, API development, Prisma ORM, and SQL optimization. They make the tradeoff clearer when a feature could be built with either MongoDB or a relational model.

A Claude Code Prompt That Produces Better Models

You are the MongoDB design reviewer for this project.
Design the model from access patterns first.

Requirements:
- Users view their recent orders in reverse chronological order.
- Order detail must show the product name, price, and category at purchase time.
- Product price changes must not rewrite historical orders.
- Admins need revenue by month, status, and item category.
- Use transactions only where partial success would break business correctness.
- Include validation schema, indexes, seed data, aggregation, explain checks, and a rollout checklist.

The answer should explicitly say which fields are embedded and which are referenced. In an order system, purchased item name, purchase price, and category are facts about the order, so embedding them is usually correct. Product inventory, current description, and merchandising data should stay in the product document and be referenced by productId.

Copy-Paste Runnable Demo

Start MongoDB locally:

docker run --name mongo-claude-demo -p 27017:27017 -d mongo:8

Install the official Node.js driver:

npm init -y
npm install mongodb
npm install -D tsx typescript
mkdir -p src

Create src/mongodb-workflow.ts:

import { MongoClient, ObjectId } from "mongodb";

const client = new MongoClient(process.env.MONGODB_URI ?? "mongodb://localhost:27017");

async function main() {
  await client.connect();
  const db = client.db("claude_code_shop_en");
  await db.dropDatabase();

  await db.createCollection("orders", {
    validator: {
      $jsonSchema: {
        bsonType: "object",
        required: ["userId", "status", "items", "totalAmount", "createdAt", "updatedAt"],
        properties: {
          userId: { bsonType: "objectId" },
          status: { enum: ["pending", "paid", "shipped", "cancelled"] },
          totalAmount: { bsonType: ["int", "long", "double", "decimal"], minimum: 0 },
          createdAt: { bsonType: "date" },
          updatedAt: { bsonType: "date" },
          items: {
            bsonType: "array",
            minItems: 1,
            items: {
              bsonType: "object",
              required: ["productId", "name", "category", "price", "quantity"],
              properties: {
                productId: { bsonType: "objectId" },
                name: { bsonType: "string" },
                category: { bsonType: "string" },
                price: { bsonType: ["int", "long", "double", "decimal"], minimum: 0 },
                quantity: { bsonType: "int", minimum: 1 }
              }
            }
          }
        }
      }
    }
  });

  const products = db.collection("products");
  const orders = db.collection("orders");

  await Promise.all([
    orders.createIndex({ userId: 1, createdAt: -1 }, { name: "orders_by_user_recent" }),
    orders.createIndex({ status: 1, createdAt: -1 }, { name: "orders_by_status_recent" }),
    orders.createIndex({ "items.category": 1, createdAt: -1 }, { name: "orders_by_category_month" }),
    products.createIndex({ category: 1, name: 1 }, { name: "products_by_category_name" })
  ]);

  const inserted = await products.insertMany([
    { name: "Claude Code Workshop", category: "training", currentPrice: 48000 },
    { name: "MongoDB Review Template", category: "template", currentPrice: 9800 }
  ]);

  const userId = new ObjectId();
  const now = new Date("2026-06-01T09:00:00.000Z");

  await orders.insertMany([
    {
      userId,
      status: "paid",
      items: [
        {
          productId: inserted.insertedIds[0],
          name: "Claude Code Workshop",
          category: "training",
          price: 48000,
          quantity: 1
        },
        {
          productId: inserted.insertedIds[1],
          name: "MongoDB Review Template",
          category: "template",
          price: 9800,
          quantity: 2
        }
      ],
      totalAmount: 67600,
      createdAt: now,
      updatedAt: now
    }
  ]);

  const report = await orders.aggregate([
    { $match: { status: { $in: ["paid", "shipped"] } } },
    { $unwind: "$items" },
    {
      $group: {
        _id: {
          month: { $dateToString: { format: "%Y-%m", date: "$createdAt" } },
          category: "$items.category"
        },
        revenue: { $sum: { $multiply: ["$items.price", "$items.quantity"] } },
        quantity: { $sum: "$items.quantity" }
      }
    },
    { $sort: { "_id.month": 1, revenue: -1 } }
  ]).toArray();

  const explain = await orders
    .find({ userId })
    .sort({ createdAt: -1 })
    .limit(10)
    .explain("executionStats");

  if (report.length !== 2) throw new Error("aggregation failed");
  if ((explain.executionStats?.totalDocsExamined ?? 0) > 1) {
    throw new Error("index was not used as expected");
  }

  console.log(JSON.stringify({ report, examined: explain.executionStats.totalDocsExamined }, null, 2));
}

main()
  .catch((error) => {
    console.error(error);
    process.exitCode = 1;
  })
  .finally(async () => {
    await client.close();
  });

Run it:

npx tsx src/mongodb-workflow.ts

This demo covers the practical workflow: validation schema, indexes, seed data, aggregation pipeline, and an explain check. When reviewing with Claude Code, paste the output and ask whether the query, sort, and index order still match the expected access pattern.

Embedding vs Reference

Use embedding when data is read together and represents the state at that moment. Use references when data changes independently, can grow without a bound, or must remain a single source of truth. Orders usually embed purchased item snapshots; products remain referenced. Tickets may embed the latest comments but reference large attachments. Audit logs should not be embedded into account documents because they grow forever.

This is the core MongoDB habit: duplicate intentionally for read performance, but document why the duplication is safe.

Indexes and Aggregation

Indexes should be derived from real queries. find({ userId }).sort({ createdAt: -1 }) maps to { userId: 1, createdAt: -1 }. Admin queues by status map to { status: 1, createdAt: -1 }. Aggregations should narrow early with $match, expand only the needed arrays with $unwind, then group and sort.

The official references are MongoDB data modeling, indexes, aggregation, transactions, and the MongoDB Node.js Driver.

Transactions Only Where They Pay for Themselves

Transactions are useful when partial success would corrupt the business process, such as marking an order paid and recording the captured payment. They are not necessary for every analytics update, notification, or search-index sync.

import { MongoClient, ObjectId } from "mongodb";

export async function markOrderPaid(client: MongoClient, orderId: ObjectId, paymentId: string) {
  const session = client.startSession();
  try {
    await session.withTransaction(async () => {
      const db = client.db("claude_code_shop_en");
      await db.collection("orders").updateOne(
        { _id: orderId, status: "pending" },
        { $set: { status: "paid", updatedAt: new Date() } },
        { session }
      );
      await db.collection("payments").insertOne(
        { orderId, paymentId, status: "captured", createdAt: new Date() },
        { session }
      );
    });
  } finally {
    await session.endSession();
  }
}

Use Atlas or a replica set when testing transaction behavior.

Common Pitfalls

The first pitfall is importing relational normalization without thinking. If every detail requires multiple application-side lookups, MongoDB becomes slower and harder to reason about.

The second is embedding unbounded arrays. Comments, logs, notifications, and history records can grow past the comfortable size of a parent document.

The third is skipping database-level validation because TypeScript already has types. Runtime data can still arrive from scripts, migrations, and admin tools.

The fourth is adding indexes without checking explain("executionStats"). If totalDocsExamined climbs with traffic, the index may not match the query shape.

The fifth is running heavy aggregation on every page view. Consider cached reports or pre-aggregated collections for dashboards.

Rollout Checklist and CTA

Before production, confirm that each top query has an index, validation is applied, seed data covers list/detail/report flows, explain has no accidental full scans, transaction boundaries are documented, and rollback steps exist.

ClaudeCodeLab can help with Claude Code training, CLAUDE.md templates, and implementation consultation for teams moving from generated MongoDB snippets to reviewed production models. The workflow in this article worked best when explain output was reviewed with Claude Code before adding more indexes.

#Claude Code #MongoDB #NoSQL #database #backend
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.