DEV Community

loading...
Cover image for MongoDb - insert embedded documents - series #06

MongoDb - insert embedded documents - series #06

functional_js profile image Functional Javascript Updated on ・3 min read

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: ""},
 ]
}

*/
Enter fullscreen mode Exit fullscreen mode

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();
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

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()
      }
    }
  )
  );
});
Enter fullscreen mode Exit fullscreen mode

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

1.
Let's fill in the form:
Alt Text

2.
Let's submit the form:
Alt Text

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]"
    ]
  }
]
*/
Enter fullscreen mode Exit fullscreen mode

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! :)

Discussion (0)

pic
Editor guide