# Mongodb Patterns

> When to activate: MongoDB, mongoose, aggregation pipeline, mongo, NoSQL, document database, Atlas

- Skill: `mattakushi432/mongodb-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/mongodb-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/mongodb-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/mongodb-patterns

---

# MongoDB Patterns

## Aggregation Pipeline

```javascript
// Sales report: group by month, filter, sort
db.orders.aggregate([
  { $match: { status: "completed", createdAt: { $gte: new Date("2024-01-01") } } },
  { $group: {
      _id: { $dateToString: { format: "%Y-%m", date: "$createdAt" } },
      total: { $sum: "$amount" },
      count: { $sum: 1 },
      avg: { $avg: "$amount" }
  }},
  { $sort: { _id: -1 } },
  { $limit: 12 }
]);

// $lookup (left join)
db.orders.aggregate([
  { $lookup: {
      from: "users",
      localField: "userId",
      foreignField: "_id",
      as: "user"
  }},
  { $unwind: "$user" },
  { $project: { amount: 1, "user.name": 1, "user.email": 1 } }
]);

// $facet — multiple aggregations in one pass
db.products.aggregate([
  { $facet: {
      byCategory: [
        { $group: { _id: "$category", count: { $sum: 1 } } }
      ],
      priceStats: [
        { $group: { _id: null, min: { $min: "$price" }, max: { $max: "$price" } } }
      ],
      total: [{ $count: "n" }]
  }}
]);
```

## Indexes

```javascript
// Single field
db.users.createIndex({ email: 1 }, { unique: true });

// Compound — order matters (equality → sort → range)
db.orders.createIndex({ userId: 1, status: 1, createdAt: -1 });

// Partial index
db.orders.createIndex({ userId: 1 }, { partialFilterExpression: { status: "pending" } });

// TTL — auto-expire documents
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 86400 });

// Text search
db.articles.createIndex({ title: "text", body: "text" }, { weights: { title: 10 } });
db.articles.find({ $text: { $search: "mongodb patterns" } },
                 { score: { $meta: "textScore" } })
           .sort({ score: { $meta: "textScore" } });

// Check index usage
db.orders.explain("executionStats").find({ userId: ObjectId("...") });
```

## Schema Design Patterns

```javascript
// Embed (1-to-few, read together)
{
  _id: ObjectId("..."),
  name: "Alice",
  addresses: [
    { type: "home", street: "123 Main St", city: "NYC" }
  ]
}

// Reference (1-to-many, independent access)
// order document
{ _id: ObjectId("..."), userId: ObjectId("..."), items: [...] }
// user document
{ _id: ObjectId("..."), name: "Alice" }

// Bucket pattern (time-series)
{
  sensorId: "temp-01",
  hour: ISODate("2024-01-15T12:00:00Z"),
  readings: [22.1, 22.3, 22.0, ...],  // up to 60 readings
  count: 60,
  min: 21.8, max: 22.5, avg: 22.1
}

// Outlier pattern — handle large arrays
{
  _id: ObjectId("..."),
  productId: ObjectId("..."),
  reviews: [...],        // first 1000
  hasMore: true          // flag for overflow documents
}
```

## Transactions (Multi-document)

```javascript
const session = client.startSession();
try {
  await session.withTransaction(async () => {
    await db.accounts.updateOne(
      { _id: fromId },
      { $inc: { balance: -amount } },
      { session }
    );
    await db.accounts.updateOne(
      { _id: toId },
      { $inc: { balance: amount } },
      { session }
    );
  });
} finally {
  await session.endSession();
}
```

## Change Streams

```javascript
// Watch collection changes
const stream = db.orders.watch([
  { $match: { "fullDocument.status": "completed" } }
], { fullDocument: "updateLookup" });

stream.on("change", async (change) => {
  console.log(change.operationType, change.fullDocument);
  await notifyFulfillment(change.fullDocument);
});

// Resume after restart
const token = await redis.get("resume_token");
const stream = db.orders.watch([], { resumeAfter: token });
stream.on("change", async (change) => {
  await redis.set("resume_token", JSON.stringify(change._id));
  process(change);
});
```

## Performance Tips

- Analyze queries: `db.collection.explain("executionStats")`
- Index selectivity: avoid low-cardinality fields as leading index key
- Projection always — never fetch full document when a subset suffices
- Use `allowDiskUse: true` for large aggregations
- Atlas Search for full-text (Lucene-backed, much faster than `$text`)
- Increase `wiredTigerCacheSizeGB` to 50–60% of RAM

