Intro
Let's say a user wants to send us some correspondence.
[aside]
Do we want to store it with the user doc?
Or store it in a doc in a separate collection?
That's the "referencing vs embedding" question.
There is a checklist of questions you can ask, and we'll cover that in depth later in the series.
The short and fast answer for this use case is that you could go either way, and it'll work fine.
[end aside]
In any case, embedding content is the power of NoSQL.
Below is a function that will do this.
First let's visualize the user data model:
/*
user data model
{
_id: "",
firstName: "",
lastName: "",
email: "",
correspondence: [
{text: "", email: "", ts: ""},
{text: "", email: "", ts: ""},
]
}
*/
The Mongo utility func
See the notes below for an explanation
/**
@func
update a doc
- by pushing an obj onto one of it's fields that is of type arr
@notes
isUpdate = false - if no match is found for the outer doc, then a new outer doc is inserted
isUpdate = true - if a match is found for the outer doc
@param {string} dbName
@param {string} collName
@param {object} objFilter - match on this criteria
@param {object} objToUpdateOrInsert - update the doc at that location with this data
@return {Promise<object>} - the updated record, whether inserted or updated
*/
const mgUpsertOnePush = async (dbName, collName, objFilter, objToUpdateOrInsert) => {
const client = await mongoAsync();
try {
const r = await client.db(dbName).collection(collName).findOneAndUpdate(objFilter, { $push: { ts: getDateStandardWithSeconds(), ...objToUpdateOrInsert } }, { upsert: true, returnOriginal: false });
l("updatedExisting (isUpdate): ", r.lastErrorObject.updatedExisting);
return r.value;
} catch (err) {
le("mgUpsertOnePush: CATCH: there was an error...\n", err.stack);
return null;
} finally {
if (client) {
client.close();
}
}
};
Notes
1.
The option, "upsert: true" is how we enable upserting in Mongo
2.
The option, "returnOriginal: false" forces Mongo to return back the doc AFTER it's been updated. We want this so we can display to the user a confirmation of what they just sent us.
3.
The Mongo command, "findOneAndUpdate" means only one doc will get modified, the first one found.
4.
The "$push" operator is the embedding power here. It pushes this doc onto one of the doc's fields that is of type arr.
5.
The log statement, that starts with "l("updatedExisting"", just tells us if an "insert" or "update" was performed. I.e. was a record found? Or is this a new record?
6.
The "le" func means "log erroro" and is just a wrapper func for "console.error"
Example Usage
app.post(apiEnum.api_upsert_user__featureRequest, async (req, res) => {
const { user_id, email, name, text} = req.body;
res.json(await mgUpsertOnePush(dbEnum.nlpdb, collEnum.users,
{ email },
{
correspondence: {
user_id,
name,
type: "featureRequest",
text,
ts: getDateStandardWithSeconds()
}
}
)
);
});
Notes
1.
We set up an Express.js route to accept correspondence from all our client applications.
2.
The client submits four fields...
user_id
email
name
text
3.
The { email } argument of the call to mgUpsertOnePush is what it tries to match on. If it doesn't find a user doc with that email, it will insert a new user doc.
This is useful if you accept correspondence from users who are not signed up already to your site.
4.
The last argument will find find a field in the user doc called, "correspondence" (or create it if it's not there already, as an arr), and will push this sub-document (which is the correspondence content) onto the end of the arr.
Test it out
3.
Confirm the DB entry:
lpromiseStringify(
mgArr(dbEnum.nlpdb, collEnum.users,
matchExact("email", "joe@sixpack.com")
)
);
/*
@output
[
{
"_id": "60203a9ef36c378955b02a9d",
"email": "joe@sixpack.com",
"correspondence": [
{
"name": "Joe Sixpack",
"type": "featureRequest",
"text": "This is my inquiry",
"ts": "[2021-02-07 Sun 11:08:13]"
}
],
"ts": [
"[2021-02-07 Sun 11:08:14]"
]
}
]
*/
What's Next?
If you have any questions, let me know.
You many notice that doing upserts, inserts, updates, delete does not involve the MongoDB Aggregation Framework (AF). However with the AF, we still have the power to insert or update its resultsets if we choose to. We don't always have to return the resultset to the client. This allows us to reshape data. We'll talk about that later in the series.
That's for reading. And let's let the journey continue! :)
Top comments (0)