Skip to content

MongoDB Reference

MongoDB is a document database — data is stored as BSON (JSON-like) documents inside collections. No fixed schema means you can iterate fast, but without indexing strategy and query patterns, you’ll hit performance walls quickly.

1. CRUD Operations

insertOne/Many, find, updateOne/Many, deleteOne — with operators
// MongoDB Node.js driver (or Mongoose)

// Insert:
await db.collection("users").insertOne({
  name: "Alice",
  email: "alice@example.com",
  age: 30,
  tags: ["beta", "mobile"],
  createdAt: new Date(),
});

await db.collection("users").insertMany([
  { name: "Bob", email: "bob@example.com", age: 25 },
  { name: "Carol", email: "carol@example.com", age: 35 },
]);

// Find:
const user = await db.collection("users").findOne({ email: "alice@example.com" });
const users = await db.collection("users").find({ age: { $gte: 25 } }).toArray();

// Query operators:
// { age: { $gte: 25 } }        — greater than or equal
// { age: { $in: [25, 30] } }   — in array
// { tags: "beta" }             — array contains "beta"
// { tags: { $all: ["beta","mobile"] } }  — array contains all
// { name: { $regex: /^al/i } } — regex match
// { $and: [{age: {$gt:18}}, {active: true}] }  — AND
// { $or: [{age: {$lt:18}}, {role: "admin"}] }  — OR

// Update:
await db.collection("users").updateOne(
  { email: "alice@example.com" },
  {
    $set: { name: "Alice Smith" },        // set fields
    $push: { tags: "enterprise" },         // append to array
    $inc: { loginCount: 1 },               // atomic increment
    $unset: { legacyField: "" },           // remove field
  }
);

await db.collection("users").updateMany(
  { active: false },
  { $set: { status: "inactive" } }
);

// Upsert (insert if not found):
await db.collection("users").updateOne(
  { email: "newuser@example.com" },
  { $setOnInsert: { createdAt: new Date() }, $set: { name: "New User" } },
  { upsert: true }
);

// Delete:
await db.collection("users").deleteOne({ _id: objectId });
await db.collection("users").deleteMany({ active: false, lastLogin: { $lt: cutoffDate } });

2. Aggregation Pipeline

$match, $group, $lookup, $project, $unwind — the stages you actually use
// Aggregation pipeline — stages execute in order:
const result = await db.collection("orders").aggregate([
  // $match — filter (put early to use indexes):
  { $match: { status: "completed", createdAt: { $gte: new Date("2025-01-01") } } },

  // $group — aggregate (like SQL GROUP BY):
  {
    $group: {
      _id: "$customerId",                    // group key
      totalSpend: { $sum: "$amount" },       // sum
      orderCount: { $count: {} },            // count
      avgAmount: { $avg: "$amount" },        // average
      lastOrder: { $max: "$createdAt" },     // max date
    }
  },

  // $lookup — join with another collection (like LEFT JOIN):
  {
    $lookup: {
      from: "customers",
      localField: "_id",                    // _id from $group above
      foreignField: "_id",
      as: "customer",
    }
  },
  { $unwind: "$customer" },                // flatten the joined array

  // $project — shape the output (1=include, 0=exclude):
  {
    $project: {
      customerName: "$customer.name",
      totalSpend: 1,
      orderCount: 1,
      avgAmount: { $round: ["$avgAmount", 2] },
    }
  },

  // $sort and $limit:
  { $sort: { totalSpend: -1 } },
  { $limit: 10 },
]).toArray();

// $unwind — explode array field into separate documents:
// Document: { tags: ["a", "b"] }
// After $unwind: { tags: "a" }, { tags: "b" }

// $facet — multiple aggregations in parallel:
const facets = await db.collection("products").aggregate([
  { $match: { active: true } },
  {
    $facet: {
      byCategory: [{ $group: { _id: "$category", count: { $count: {} } } }],
      priceRange: [{ $group: { _id: null, min: { $min: "$price" }, max: { $max: "$price" } } }],
    }
  }
]).toArray();

3. Mongoose (Node.js ODM)

Schema definition, validation, virtuals, middleware, and population
import mongoose, { Schema, Document, model } from "mongoose";

// Schema definition:
const userSchema = new Schema({
  name: { type: String, required: true, trim: true, maxlength: 100 },
  email: { type: String, required: true, unique: true, lowercase: true },
  age: { type: Number, min: 0, max: 150 },
  role: { type: String, enum: ["user", "admin"], default: "user" },
  tags: [String],
  address: {
    city: String,
    country: { type: String, default: "US" },
  },
  createdAt: { type: Date, default: Date.now },
});

// Virtual (computed field — not stored in DB):
userSchema.virtual("displayName").get(function() {
  return `${this.name} (${this.email})`;
});

// Pre-save middleware (hooks):
userSchema.pre("save", async function(next) {
  if (this.isModified("password")) {
    this.password = await bcrypt.hash(this.password, 12);
  }
  next();
});

// Instance method:
userSchema.methods.comparePassword = async function(plain) {
  return bcrypt.compare(plain, this.password);
};

// Static method:
userSchema.statics.findByEmail = function(email) {
  return this.findOne({ email: email.toLowerCase() });
};

const User = model("User", userSchema);

// Queries:
const user = await User.findById(id);
const users = await User.find({ role: "admin" }).sort("-createdAt").limit(10);
const user2 = await User.findByIdAndUpdate(id, { $set: data }, { new: true, runValidators: true });

// Population (join via ref):
const postSchema = new Schema({
  title: String,
  author: { type: Schema.Types.ObjectId, ref: "User" },   // reference
});
const post = await Post.findById(id).populate("author", "name email");
// post.author is now a full User document

4. Indexes

Single field, compound, text, sparse, TTL, and partial indexes
// Always index fields used in $match, $sort, and $lookup localField
// createIndex is idempotent — safe to call on every startup

// Single field:
await db.collection("users").createIndex({ email: 1 }, { unique: true });
await db.collection("orders").createIndex({ createdAt: -1 });  // -1 = descending

// Compound (order matters — put equality fields first, then range, then sort):
await db.collection("orders").createIndex(
  { customerId: 1, status: 1, createdAt: -1 },
  { name: "orders_customer_status_date" }
);

// Text index (full-text search):
await db.collection("products").createIndex(
  { name: "text", description: "text" },
  { weights: { name: 10, description: 1 } }   // name matches score 10x higher
);
// Query: { $text: { $search: "wireless headphones" } }
// Sort by relevance: { score: { $meta: "textScore" } }

// TTL index (auto-delete documents after N seconds):
await db.collection("sessions").createIndex(
  { expiresAt: 1 },
  { expireAfterSeconds: 0 }    // delete when expiresAt is in the past
);

// Sparse index (only index documents where the field exists):
await db.collection("users").createIndex(
  { googleId: 1 },
  { sparse: true, unique: true }    // skip docs without googleId
);

// Partial index (index subset of documents — smaller, faster):
await db.collection("orders").createIndex(
  { amount: 1 },
  { partialFilterExpression: { status: "pending" } }   // only pending orders
);

// Check index usage with explain:
await db.collection("orders")
  .find({ customerId: "123" })
  .explain("executionStats");
// Look for: "IXSCAN" (good) vs "COLLSCAN" (full scan = missing index)

5. Connection Pooling, Transactions & Production Patterns

MongoClient pool, multi-document transactions, change streams, and replica sets
import { MongoClient } from "mongodb";

// Connection pool (singleton — reuse across requests):
let client: MongoClient;

export async function getDb() {
  if (!client) {
    client = new MongoClient(process.env.MONGODB_URI!, {
      maxPoolSize: 50,              // max concurrent connections
      minPoolSize: 5,               // keep minimum connections warm
      serverSelectionTimeoutMS: 5000,
      socketTimeoutMS: 45000,
    });
    await client.connect();
  }
  return client.db("myapp");
}

// Multi-document transaction (ACID — requires replica set):
async function transferFunds(fromId, toId, amount) {
  const session = client.startSession();
  try {
    session.startTransaction();
    await db.collection("accounts").updateOne(
      { _id: fromId },
      { $inc: { balance: -amount } },
      { session }
    );
    await db.collection("accounts").updateOne(
      { _id: toId },
      { $inc: { balance: amount } },
      { session }
    );
    await session.commitTransaction();
  } catch (err) {
    await session.abortTransaction();
    throw err;
  } finally {
    session.endSession();
  }
}

// Change streams (real-time events):
const changeStream = db.collection("orders").watch([
  { $match: { operationType: "insert", "fullDocument.status": "pending" } }
]);
changeStream.on("change", (change) => {
  console.log("New pending order:", change.fullDocument);
  notifyWorkers(change.fullDocument);
});

// GridFS (store large files in MongoDB):
// const bucket = new GridFSBucket(db);
// const uploadStream = bucket.openUploadStream("filename.pdf");

Track MongoDB and database releases at ReleaseRun. Related: PostgreSQL Advanced SQL | Redis Reference | Express.js Reference

🔍 Free tool: npm Package Health Checker — check mongoose, mongodb, and other Node.js MongoDB packages for known CVEs and active maintenance.

Founded

2023 in London, UK

Contact

hello@releaserun.com