MongoDB Upsert Operations
Introduction
When working with MongoDB, there are situations where you need to update a document if it exists, but create it if it doesn't. This common operation pattern could be implemented using a find-then-update or find-then-insert approach, but MongoDB offers a more elegant solution: upsert.
An upsert (update + insert) is a special type of update operation that:
- Updates the document if it matches the filter criteria
- Creates a new document if no document matches the filter criteria
This functionality simplifies your code and makes your database operations more efficient by eliminating the need for separate find and conditional update/insert logic.
Understanding Upserts in MongoDB
Upsert operations are executed using the same methods as regular updates but with a special option enabled. The two main methods for performing upserts are:
updateOne()
withupsert: true
- Updates a single document or creates one if none existupdateMany()
withupsert: true
- Updates multiple documents matching the criteria, or creates one if none exist
Let's explore how upserts work with some examples.
Basic Upsert Example
Let's say we have a product inventory system and want to either update an existing product or add it if it doesn't exist.
// Upsert operation for a product
db.products.updateOne(
{ productCode: "P1001" }, // filter criteria
{ // update operations
$set: {
name: "Wireless Keyboard",
price: 59.99,
category: "Computer Accessories",
inStock: 45
}
},
{ upsert: true } // enables upsert
);
This operation will:
- Look for a document with
productCode: "P1001"
- If found, update its fields with the values in
$set
- If not found, create a new document with
productCode: "P1001"
and all the fields specified in$set
How Upsert Creates New Documents
When an upsert creates a new document, two sources of data are used:
- The filter criteria - fields and values from the query filter
- The update operations - fields and values from update operators like
$set
For example:
// No document with productCode "P2002" exists yet
db.products.updateOne(
{ productCode: "P2002", category: "Electronics" },
{ $set: { name: "Bluetooth Speaker", price: 89.99 } },
{ upsert: true }
);
If this creates a new document, it will contain:
productCode: "P2002"
(from the filter)category: "Electronics"
(from the filter)name: "Bluetooth Speaker"
(from the update operation)price: 89.99
(from the update operation)
Using Update Operators with Upserts
Upserts work with all MongoDB update operators. Here are examples with some common operators:
Using $set with Upsert
// Update a user's address or create the user if they don't exist
db.users.updateOne(
{ email: "[email protected]" },
{
$set: {
"address.street": "123 Main St",
"address.city": "Boston",
"address.state": "MA",
"address.zip": "02101"
}
},
{ upsert: true }
);
Using $inc with Upsert
// Increment a product's view count or create it with initial count
db.products.updateOne(
{ productCode: "P3003" },
{
$inc: { viewCount: 1 },
$set: { lastViewed: new Date() }
},
{ upsert: true }
);
This is particularly useful for counters and statistics, as you don't need to check if the document exists first.
Using $push with Upsert
// Add a comment to an article or create the article with initial comment
db.articles.updateOne(
{ slug: "mongodb-upsert-guide" },
{
$push: {
comments: {
user: "user123",
text: "Great article!",
createdAt: new Date()
}
},
$setOnInsert: {
title: "MongoDB Upsert Guide",
createdAt: new Date()
}
},
{ upsert: true }
);
Special Operator: $setOnInsert
The $setOnInsert
operator is especially useful with upserts. It sets values for fields only when an insert occurs, not during updates.
// Update a user's last login, or create a new user with registration date
db.users.updateOne(
{ username: "newuser" },
{
$set: { lastLogin: new Date() },
$setOnInsert: {
registeredAt: new Date(),
accountStatus: "active",
roles: ["user"]
}
},
{ upsert: true }
);
In this example:
- If the user exists, only
lastLogin
is updated - If the user doesn't exist, all fields are set including the ones in
$setOnInsert
This is very useful for setting initialization values or creation timestamps that should never be modified during updates.
Real-World Application: Session Management
Let's look at a practical example of using upserts for managing user sessions in a web application:
// Function to track user sessions
async function trackUserSession(userId, sessionData) {
const result = await db.sessions.updateOne(
{ userId: userId },
{
$set: {
lastActive: new Date(),
ipAddress: sessionData.ip,
userAgent: sessionData.userAgent
},
$setOnInsert: {
createdAt: new Date(),
firstIp: sessionData.ip
},
$addToSet: {
visitedPages: sessionData.currentPage
}
},
{ upsert: true }
);
return result;
}
This function:
- Updates the existing session if the user already has one
- Creates a new session if this is the user's first visit
- Tracks the pages they visit without duplicates (using
$addToSet
) - Always maintains the original creation time and first IP
Performance Considerations
Upserts are generally more efficient than separate find-then-insert/update operations because:
- They require only one round-trip to the server
- They eliminate race conditions where documents might be created twice
- They reduce code complexity by handling both cases in a single operation
However, be aware that:
- Upserts perform additional work to check if a document exists
- Large upserts with complex criteria might be slower than direct inserts when you know the document doesn't exist
When to Use Upserts
Upserts are ideal for:
- Idempotent operations - operations that can be applied multiple times without changing the result
- First-time initialization - create documents with default values on first access
- Counters and statistics - increment counters without checking if they exist first
- Caching operations - store computed data with a lookup key
- Config systems - ensure configuration exists with defaults
Upsert Flow Diagram
Common Pitfalls and How to Avoid Them
1. Unexpected Document Creation
When using complex queries with upsert, be careful about which fields end up in the created document.
// This might create documents with unexpected field values
db.inventory.updateOne(
{ quantity: { $lt: 10 } }, // Problematic for new documents!
{ $set: { reorder: true } },
{ upsert: true }
);
If no document with quantity < 10
exists, this creates a document with { quantity: { $lt: 10 }, reorder: true }
, which isn't valid for comparisons.
Solution: Always use equality conditions in filters for upserts.
2. Missing Required Fields
If your schema requires certain fields, an upsert might create incomplete documents.
Solution: Use $setOnInsert
to provide all required fields.
db.users.updateOne(
{ email: "[email protected]" },
{
$set: { lastActive: new Date() },
$setOnInsert: {
username: "new_user",
createdAt: new Date(),
status: "pending"
// Include all required fields
}
},
{ upsert: true }
);
Summary
MongoDB's upsert operations provide an elegant solution to the common pattern of updating existing documents or creating new ones when needed. By using the upsert: true
option with update methods, you can:
- Write more concise, efficient code
- Eliminate race conditions in find-then-update patterns
- Reduce database round-trips
- Handle both new and existing documents in a single operation
The $setOnInsert
operator further enhances upserts by allowing you to specify fields that should only be set when creating new documents, making this feature even more powerful for real-world applications.
Exercises
-
Basic Upsert: Write an upsert operation that updates a product's price if it exists or creates it with default values if it doesn't.
-
Counter Implementation: Create a page view counter that increments each time a page is viewed, using upserts to handle new pages.
-
User Preferences: Implement a function that saves user preferences, creating default preferences for new users while preserving existing ones.
-
Advanced: Write an upsert operation that tracks daily login statistics, creating a new document for each day and incrementing counters for returning users.
Additional Resources
Happy coding with MongoDB upserts!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)