Episode 3 — NodeJS MongoDB Backend Architecture / 3.11 — Database Optimization

3.11.d — MongoDB Operators

MongoDB operators are the building blocks of every query, update, and aggregation. Mastering them gives you precise control over how you read, filter, and modify data — from simple comparisons to complex array manipulations.


< 3.11.c — Aggregation Pipeline | 3.11.e — Advanced Aggregation >


1. Comparison Operators

Used in queries to compare field values:

OperatorMeaningExample
$eqEqual to{ age: { $eq: 25 } }
$neNot equal to{ status: { $ne: "deleted" } }
$ltLess than{ price: { $lt: 100 } }
$gtGreater than{ price: { $gt: 50 } }
$lteLess than or equal{ age: { $lte: 65 } }
$gteGreater than or equal{ age: { $gte: 18 } }
$inMatches any value in array{ status: { $in: ["active", "pending"] } }
$ninMatches none of the values{ role: { $nin: ["banned", "suspended"] } }

Examples

// $eq — explicit equality (usually shorthand is used)
db.users.find({ age: { $eq: 25 } });
db.users.find({ age: 25 });              // Shorthand — same result

// $ne — not equal
db.products.find({ status: { $ne: "discontinued" } });
// Returns all products where status is NOT "discontinued"
// Also returns docs where status field does not exist

// $lt, $gt — range queries
db.products.find({ price: { $gt: 10, $lt: 100 } });
// Price between 10 (exclusive) and 100 (exclusive)

// $lte, $gte — inclusive range
db.users.find({ age: { $gte: 18, $lte: 65 } });
// Age between 18 and 65, inclusive

// $in — matches any value in the array
db.orders.find({ status: { $in: ["shipped", "delivered"] } });
// Equivalent to: status === "shipped" || status === "delivered"

// $nin — does not match any value
db.users.find({ role: { $nin: ["admin", "superadmin"] } });
// Excludes admins and superadmins

// Date comparisons
db.orders.find({
  createdAt: {
    $gte: new Date("2025-01-01"),
    $lt: new Date("2025-02-01")
  }
});
// All orders from January 2025

2. Logical Operators

Combine multiple conditions:

OperatorDescriptionSyntax
$andAll conditions must match{ $and: [ {cond1}, {cond2} ] }
$orAt least one condition must match{ $or: [ {cond1}, {cond2} ] }
$notNegates a condition{ field: { $not: { operator } } }
$norNone of the conditions match{ $nor: [ {cond1}, {cond2} ] }

Examples

// $and — all conditions (implicit AND is more common)
db.products.find({
  $and: [
    { price: { $gte: 10 } },
    { price: { $lte: 100 } },
    { category: "electronics" }
  ]
});

// Implicit AND — shorter syntax (same result)
db.products.find({
  price: { $gte: 10, $lte: 100 },
  category: "electronics"
});

// When you NEED explicit $and — same field with different operators
db.products.find({
  $and: [
    { $or: [{ category: "electronics" }, { category: "books" }] },
    { $or: [{ price: { $lt: 20 } }, { onSale: true }] }
  ]
});
// (electronics OR books) AND (cheap OR on sale)

// $or — at least one condition
db.users.find({
  $or: [
    { email: "alice@example.com" },
    { username: "alice" }
  ]
});

// $not — negate a condition
db.products.find({
  price: { $not: { $gt: 100 } }
});
// Matches price <= 100 AND documents where price does not exist

// $nor — none of the conditions match
db.users.find({
  $nor: [
    { status: "banned" },
    { status: "deleted" }
  ]
});
// Not banned AND not deleted (includes docs without status field)

3. Array Operators (Query)

Used to query documents based on array field contents:

OperatorDescriptionExample
$elemMatchArray element matches all conditions{ scores: { $elemMatch: { $gte: 80, $lte: 90 } } }
$allArray contains all specified elements{ tags: { $all: ["tech", "sale"] } }
$sizeArray has exact number of elements{ tags: { $size: 3 } }

Examples

// $elemMatch — element must match ALL conditions
// Document: { name: "Alice", scores: [72, 85, 91, 68] }
db.students.find({
  scores: { $elemMatch: { $gte: 80, $lte: 90 } }
});
// Matches: 85 is >= 80 AND <= 90

// Without $elemMatch — conditions can match DIFFERENT elements
db.students.find({
  scores: { $gte: 80, $lte: 90 }
});
// This could match if ANY element >= 80 and ANY element <= 90
// (not necessarily the same element)

// $elemMatch with objects in arrays
// Document: { items: [{ product: "A", qty: 5 }, { product: "B", qty: 2 }] }
db.orders.find({
  items: { $elemMatch: { product: "A", qty: { $gte: 3 } } }
});
// Matches only if ONE item is product "A" WITH qty >= 3

// $all — array must contain ALL specified values
db.products.find({ tags: { $all: ["electronics", "wireless", "new"] } });
// Array must contain "electronics" AND "wireless" AND "new"
// Order does not matter, extra elements are OK

// $size — exact array length
db.users.find({ roles: { $size: 2 } });
// Only users with exactly 2 roles
// NOTE: $size does not accept ranges — cannot do { $size: { $gte: 2 } }

// Workaround for array length ranges (use aggregation or $expr):
db.users.find({
  $expr: { $gte: [{ $size: "$roles" }, 2] }
});

4. Array Update Operators

Used in update operations to modify arrays:

OperatorDescriptionExample
$pushAdd element to array{ $push: { tags: "new" } }
$pullRemove matching elements{ $pull: { tags: "old" } }
$popRemove first (-1) or last (1) element{ $pop: { tags: 1 } }
$addToSetAdd only if not already present{ $addToSet: { tags: "sale" } }

Examples

// $push — add element to array
db.users.updateOne(
  { _id: userId },
  { $push: { notifications: { message: "Welcome!", date: new Date() } } }
);

// $push with modifiers
db.users.updateOne(
  { _id: userId },
  { $push: {
    scores: {
      $each: [85, 92, 78],    // Add multiple values
      $sort: -1,               // Sort array descending after push
      $slice: 10               // Keep only top 10 scores
    }
  }}
);

// $addToSet — add only if unique
db.products.updateOne(
  { _id: productId },
  { $addToSet: { tags: "featured" } }
);
// If "featured" already exists, no change

// $addToSet with $each — add multiple unique values
db.products.updateOne(
  { _id: productId },
  { $addToSet: { tags: { $each: ["sale", "new", "popular"] } } }
);

// $pull — remove matching elements
db.users.updateOne(
  { _id: userId },
  { $pull: { notifications: { read: true } } }
);
// Removes ALL notifications where read === true

// $pull with condition
db.users.updateOne(
  { _id: userId },
  { $pull: { scores: { $lt: 50 } } }
);
// Removes all scores less than 50

// $pop — remove first or last element
db.users.updateOne(
  { _id: userId },
  { $pop: { activityLog: 1 } }    // Remove last element
);
db.users.updateOne(
  { _id: userId },
  { $pop: { activityLog: -1 } }   // Remove first element
);

Positional Operator ($)

// Update a specific array element that matches a condition
db.orders.updateOne(
  { _id: orderId, "items.productId": productId },
  { $set: { "items.$.quantity": 5 } }
);
// $ refers to the first matching array element

// Update all matching array elements ($[])
db.orders.updateMany(
  { status: "pending" },
  { $set: { "items.$[].shipped": false } }
);

// Filtered positional operator ($[<identifier>])
db.orders.updateOne(
  { _id: orderId },
  { $set: { "items.$[elem].discount": 10 } },
  { arrayFilters: [{ "elem.price": { $gte: 100 } }] }
);
// Apply discount only to items with price >= 100

5. Update Operators

Used in updateOne(), updateMany(), and findOneAndUpdate():

OperatorDescriptionExample
$setSet field value{ $set: { name: "Alice" } }
$unsetRemove a field{ $unset: { tempField: "" } }
$incIncrement by value{ $inc: { count: 1 } }
$mulMultiply by value{ $mul: { price: 1.1 } }
$renameRename a field{ $rename: { "old": "new" } }
$minSet if value is less than current{ $min: { lowScore: 50 } }
$maxSet if value is greater than current{ $max: { highScore: 99 } }
$currentDateSet to current date{ $currentDate: { updatedAt: true } }

Examples

// $set — set one or more fields
db.users.updateOne(
  { _id: userId },
  { $set: {
    name: "Alice Smith",
    "address.city": "New York",      // Dot notation for nested fields
    updatedAt: new Date()
  }}
);

// $unset — remove fields
db.users.updateOne(
  { _id: userId },
  { $unset: { temporaryToken: "", oldField: "" } }
);
// Field value in $unset does not matter — field is removed regardless

// $inc — increment (positive or negative)
db.products.updateOne(
  { _id: productId },
  { $inc: {
    viewCount: 1,       // Increment by 1
    stock: -1            // Decrement by 1
  }}
);

// $mul — multiply
db.products.updateMany(
  { category: "electronics" },
  { $mul: { price: 0.9 } }   // 10% discount
);

// $rename — rename a field
db.users.updateMany(
  {},
  { $rename: { "fullName": "name", "pwd": "password" } }
);

// $min / $max — conditional update
db.scores.updateOne(
  { playerId: id },
  {
    $min: { lowestScore: newScore },   // Only updates if newScore < current
    $max: { highestScore: newScore }   // Only updates if newScore > current
  }
);

// $currentDate — set to current timestamp
db.posts.updateOne(
  { _id: postId },
  {
    $set: { title: "Updated Title" },
    $currentDate: {
      updatedAt: true,                          // Sets as Date
      "metadata.lastModified": { $type: "timestamp" }  // Sets as Timestamp
    }
  }
);

// Combining multiple update operators
db.users.updateOne(
  { _id: userId },
  {
    $set: { lastLogin: new Date() },
    $inc: { loginCount: 1 },
    $push: { loginHistory: { date: new Date(), ip: "192.168.1.1" } },
    $addToSet: { devices: "mobile" }
  }
);

6. Element Operators

Check field existence or type:

OperatorDescriptionExample
$existsField exists or not{ phone: { $exists: true } }
$typeField is a specific BSON type{ age: { $type: "number" } }

Examples

// $exists — check if field exists
db.users.find({ phone: { $exists: true } });
// Only users that have a phone field (even if null)

db.users.find({ deletedAt: { $exists: false } });
// Users without a deletedAt field (not soft-deleted)

// $type — check field type
db.users.find({ age: { $type: "number" } });
// Only documents where age is a number

db.mixedData.find({ value: { $type: "string" } });
// Common types: "string", "number", "bool", "object", "array", "null", "date", "objectId"

// Multiple types
db.data.find({ field: { $type: ["string", "number"] } });
// Field is either string or number

7. Regex Operator

Pattern matching on string fields:

// $regex — regular expression matching
db.users.find({ name: { $regex: /alice/i } });
// Case-insensitive search for "alice" anywhere in name

// Explicit syntax
db.users.find({ name: { $regex: "^Alice", $options: "i" } });
// Names starting with "Alice" (case-insensitive)

// Common patterns
db.users.find({ email: { $regex: /@gmail\.com$/ } });           // Gmail users
db.products.find({ name: { $regex: /^wireless/i } });           // Starts with "wireless"
db.logs.find({ message: { $regex: /error|fail|exception/i } }); // Contains error terms

// Mongoose — can also use RegExp directly
const users = await User.find({ name: /alice/i });
const gmailUsers = await User.find({ email: /@gmail\.com$/i });

Performance notes:

  • Prefix regex (/^alice/) can use an index — fast
  • Non-prefix regex (/alice/) cannot use an index efficiently — slow on large collections
  • For full-text search, prefer text indexes or Atlas Search over regex

8. Operators Quick Reference Table

Query Operators

CategoryOperatorPurpose
Comparison$eqEqual
Comparison$neNot equal
Comparison$gt / $gteGreater than / or equal
Comparison$lt / $lteLess than / or equal
Comparison$in / $ninIn array / not in array
Logical$andAll conditions true
Logical$orAny condition true
Logical$notNegate condition
Logical$norNo condition true
Array$elemMatchElement matches all criteria
Array$allContains all values
Array$sizeExact array length
Element$existsField exists
Element$typeField is BSON type
Regex$regexPattern match

Update Operators

CategoryOperatorPurpose
Field$setSet value
Field$unsetRemove field
Field$incIncrement
Field$mulMultiply
Field$renameRename field
Field$min / $maxConditional set
Field$currentDateSet current date
Array$pushAdd to array
Array$pullRemove from array
Array$popRemove first/last
Array$addToSetAdd unique to array

Key Takeaways

  1. Comparison operators ($eq, $gt, $in, etc.) are the foundation of every query filter
  2. Logical operators ($and, $or) combine conditions — implicit $and is more common
  3. Array query operators ($elemMatch, $all, $size) handle complex array conditions
  4. Array update operators ($push, $pull, $addToSet) safely modify array contents
  5. Update operators ($set, $inc, $unset) perform atomic field-level changes
  6. $exists and $type are essential for schema flexibility in MongoDB
  7. Prefix regex can use indexes; non-prefix regex should be avoided on large collections
  8. Combine multiple update operators in a single update for atomic multi-field changes

Explain-It Challenge

You are building a social media API. Users have profiles with arrays of followers, following, posts, and nested settings objects. Design the following operations using appropriate operators: (1) find users who follow at least 3 specific accounts AND have more than 100 followers, (2) add a new follower without duplicates, (3) remove a specific post from a user's posts array, (4) update a nested notification setting without affecting other settings, (5) find users whose bio matches a search term but are not banned. For each, explain which operators you chose and why.