Claude Code NoSQL/MongoDB Guide: Schema, Indexes, Aggregation
Design MongoDB with Claude Code from access patterns to validation, indexes, aggregation, tests, and rollout.
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 case | Common reads | Good MongoDB shape | Main risk |
|---|---|---|---|
| E-commerce orders | recent orders by user, order detail, revenue by month | embed purchased item name and price, keep productId reference | rewriting order history when product data changes |
| SaaS audit logs | organization, user, date range, event type | append-only documents with compound indexes and possible TTL | slow scans on large log collections |
| CMS articles | lookup by slug, list by status, category archives | explicit slug, status, and date indexes | exposing drafts or internal notes |
| Support tickets | customer queue, assigned agent, recent comments | embed bounded recent comments, reference large attachments | unbounded 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.
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.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Obsidian to CLAUDE.md Workflow: Stop Re-explaining Context
Turn Obsidian working notes into concise CLAUDE.md operating notes that make Claude Code sessions easier to resume.
Claude Code Revenue CTA Routing: Send Articles to PDF, Gumroad, and Consultation
A Claude Code workflow for routing article readers to the free PDF, Gumroad products, or consultation by intent.
Claude Code Team Handoff Rules: Review Evidence, Permissions, Rollback, and Revenue Paths
A practical Claude Code handoff format for team review, proof, permission rules, rollback, free PDF, Gumroad, and consultation paths.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.