MongoDB

58MongoDB (NoSQL testing)

What you will master here

  • Document model vs relational
  • BSON, _id, ObjectId
  • CRUD: insertOne/Many, find, updateOne/Many, deleteOne/Many
  • Query operators: $eq/$gt/$in/$regex/$exists
  • Update operators: $set/$inc/$push/$pull
  • Aggregation pipeline
  • Indexes (single, compound, text, TTL)
  • Transactions, replica sets, sharding (overview)
  • Testing with mongo-memory-server / Testcontainers

58.1 Document model

{
  "_id": ObjectId("..."),
  "email": "alice@test.com",
  "name": "Alice",
  "addresses": [
    { "city": "BLR", "zip": "560001" },
    { "city": "BOM", "zip": "400001" }
  ],
  "createdAt": ISODate("2026-06-19T00:00:00Z")
}

Schemaless by default. Documents in the same collection can have different shapes. Schema validation can be enforced via JSON Schema on the collection.

58.2 CRUD essentials

// Connect
const { MongoClient } = require('mongodb');
const client = await MongoClient.connect('mongodb://localhost:27017');
const db = client.db('app');

// Insert
await db.collection('users').insertOne({ email: 'a@test.com', name: 'Alice' });
await db.collection('users').insertMany([{...}, {...}]);

// Find
await db.collection('users').findOne({ email: 'a@test.com' });
const users = await db.collection('users')
  .find({ active: true, age: { $gt: 18 } })
  .sort({ createdAt: -1 })
  .limit(20)
  .toArray();

// Update
await db.collection('users').updateOne(
  { _id }, { $set: { name: 'Alice S.' }, $inc: { loginCount: 1 } }
);

// Delete
await db.collection('users').deleteOne({ _id });
await db.collection('users').deleteMany({ active: false });

58.3 Query operators

OperatorMeaning
$eq, $neequal / not equal
$gt, $gte, $lt, $ltecomparison
$in, $ninin/not in array
$existsfield present
$regexregex match
$and, $or, $not, $norlogical
$elemMatcharray element matches all conditions
$sizearray length

58.4 Aggregation pipeline

// Total spend per user with active orders
db.collection('orders').aggregate([
  { $match: { status: 'paid' } },
  { $group: { _id: '$userId', total: { $sum: '$amount' }, count: { $sum: 1 } } },
  { $sort: { total: -1 } },
  { $limit: 10 },
  { $lookup: {
      from: 'users',
      localField: '_id',
      foreignField: '_id',
      as: 'user',
  }},
  { $project: { total: 1, count: 1, email: { $first: '$user.email' } } },
]).toArray();

58.5 Indexes

await db.collection('users').createIndex({ email: 1 }, { unique: true });
await db.collection('orders').createIndex({ userId: 1, createdAt: -1 }); // compound
await db.collection('posts').createIndex({ title: 'text', body: 'text' }); // full-text
await db.collection('sessions').createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }); // TTL

Explain

db.collection('users').find({ email: 'x' }).explain('executionStats');
// stage = IXSCAN → uses index. COLLSCAN → full scan, slow on large data.

58.6 Transactions

const session = client.startSession();
try {
  await session.withTransaction(async () => {
    await db.collection('orders').insertOne({...}, { session });
    await db.collection('inventory').updateOne({ sku }, { $inc: { qty: -1 }}, { session });
  });
} finally { await session.endSession(); }

Requires replica set or sharded cluster. Default single-node MongoDB doesn't support transactions.

58.7 Testing patterns

import { MongoMemoryServer } from 'mongodb-memory-server';
let mongo: MongoMemoryServer;
beforeAll(async () => {
  mongo = await MongoMemoryServer.create();
  process.env.MONGO_URL = mongo.getUri();
});
afterAll(async () => mongo.stop());

58.8 SQL → MongoDB mapping

SQLMongoDB
DatabaseDatabase
TableCollection
RowDocument
ColumnField
JOIN$lookup (in aggregation)
Primary key_id (ObjectId default)
IndexIndex
TransactionMulti-doc transaction (replica set)

Module 63 — MongoDB Q&A

Document vs relational model?
Relational: rigid schema, normalised tables, joins. Document: flexible schema per collection, nested arrays/objects allowed, joins are explicit ($lookup) and discouraged. Document suits read-heavy, denormalised data; relational suits transactional integrity.
What's an ObjectId?
12-byte default _id: 4 bytes timestamp + 5 random + 3 counter. Time-orderable, unique without coordination. Looks like ObjectId('6650...').
How do you find slow queries?
.explain('executionStats') shows the plan. Look for COLLSCAN (bad — full scan) vs IXSCAN (good — uses index). Mongo profiler logs slow queries above a threshold.
How do you JOIN in MongoDB?
$lookup in the aggregation pipeline. Optionally followed by $unwind to flatten array results. Less efficient than RDBMS joins; design for denormalised reads.
When do you need a transaction?
When multiple documents must update atomically — e.g. decrement inventory + create order. Single-document operations are already atomic. Transactions need a replica set or sharded cluster.
What is a TTL index?
An index with expireAfterSeconds — Mongo automatically deletes documents whose indexed field is older than the threshold. Used for sessions, OTP codes, cache.
Compound index — does order matter?
Yes. Mongo uses a compound index left-to-right (prefix rule). An index on { a: 1, b: 1 } helps queries on { a } and { a, b } but NOT { b } alone.
How do you write fast Mongo tests?
mongodb-memory-server spawns an in-memory mongod per suite — boots in <1s, no Docker. Per-test cleanup drops collections. For higher fidelity (transactions, replica-set features), use Testcontainers.
What's the aggregation pipeline?
Sequence of stages ($match, $group, $project, $lookup, $sort, $limit, etc.) that transform documents. Each stage feeds into the next. Equivalent of SQL GROUP BY + JOIN + computed fields but in a pipeline DSL.