I recently had to deal with a small issue working with update operations and aggregation pipelines in MongoDB. I couldn't find much help online to fix it, but I finally managed to understand it and decided to write my findings down to help anyone that may come across the same issue in the future.
The code
I was trying to create an update operation using aggregation pipelines on a Node.js project with mongoose. My code looked something like this:
// Model schema
let productSchema = new Schema({
name: { type: String, required: true },
description: { type: String, required: false },
currency: { type: String, required: false },
price: { type: Number, required: true },
createdAt: { type: Date, default: Date.now, required: true },
updatedAt: { type: Date, required: false },
tags: [String],
});
// Update operation
module.exports.update = async (product) => {
await productModel.findByIdAndUpdate(product._id,
[{
$set:
{
name: product.name,
description: product.description,
currency: product.currency,
price: product.price,
updatedAt: '$$NOW',
tags: { $concatArrays: [ '$tags', product.newTags ] }
}
}],
{
runValidators: true
});
}
The previous code is supposed to find a product by id and update its properties, setting the current date in the updatedAt
property and concatenating the existent tags with some new tags.
Looks fine, right?
The error
But when I executed the update function, the following error was thrown by MongoDB:
{
"ok": 0,
"errmsg": "'$' by itself is not a valid FieldPath",
"code": 16872,
"codeName": "Location16872",
"name": "MongoError"
}
So after some debugging and tests I realized where the issue was. First let me show you how the product
object that I was receiving in the arguments looked like:
{
"name": "Lenovo Thinkbook 13s",
"description": "The ThinkBook 13s laptop is a lightweight and durable, business laptop with amazing entertainment features.",
"currency": "$",
"price": 723,
"newTags": [ "laptop", "lenovo" ]
}
The issue was in the currency
property value. The value was "$", which is a character used by MongoDB to identify field paths. So the error message was actually really clear, but it was not obvious for me at first sight.
The solution
Luckily the solution to this issue was pretty simple, I just had to use the $literal
operator to wrap up my currency
new value, like this:
module.exports.update = async (product) => {
await productModel.findByIdAndUpdate(product._id,
[{
$set:
{
name: product.name,
description: product.description,
currency: { $literal: product.currency }, // <--- here!
price: product.price,
updatedAt: '$$NOW',
tags: { $concatArrays: [ '$tags', product.newTags ] }
}
}],
{
runValidators: true
});
}
About $literal
$literal
is an aggregation pipeline operator that receives a value and makes sure the aggregation pipeline doesn't interpret it as a field path, and uses its literal value instead.
Read more about this operator in MongoDB's docs.
Test repo
I created a github repo to test the issue and its solution:
pawap90 / mongoose-literal-examples
A Node.js + Mongoose + Jest project that demonstrates how to use MongoDB's $literal operator to fix some common issues.
A Node.js + Mongoose + Jest project that demonstrates how to use MongoDB's $literal
operator to fix some common issues.
This repo was build as an example for my article Fixing MongoDB's error "'$' by itself is not a valid FieldPath".
Dependencies
What you need to run this project:
- Node.js
(MongoDB is not required because it'll run in memory by the package mongodb-memory-server
).
Try it out
1. Install dependencies
npm install
2. Run tests
npm test
Contribute
Feel free to contribute to this project either by leaving your comments and suggestions in the Issues section or creating a PR.
Testing MongoDB in memory
To learn more about how this project executes tests on a dynamically generated database in memory, check my article Testing Node.js + Mongoose with an in-memory database
Thoughts? 💬
Have you ever come across this issue? Was this article useful?
Top comments (4)
Unrelated to your post but it has been a while since you blogged, so, welcome back!.
Thank you! 🤗 I'm really happy to be back.
I probably won't be writing every week as I used to, but I already have a few ideas of stuff I want to write about in the near future, so I'll try not to disappear for so long again 😅
Thanks, haven't come across this one, as we model currency according to ISO. But it's nice to know there is an easy solution, I hope I'll remember it when I do need it.
Glad to help! I only realize about this issue accidentally because some client wanted the default (local) currency to be represented as "$" and I just thought "yeah, ok, no big deal" 😅