改善
Kaizen  · Today I Learned by Ville Säävuori

Automatic Last Modified Field With MongoDB

MongoDB Realm is a great Firebase alternative that is backed by a “proper” database. I was missing automatic created_at and last_modified_at helpers from Django in a MongoDB project so I implemented them using triggers. Turns out it wasn’t as straightforward as I thought but still pretty simple in the end.

MongoDB Realm has very powerful triggers which are JavaScript functions that operate on a linked Atlas (a hosted MongoDB) cluster. The possibilities with these are almost limitless — you can even install your own npm packages! The only tricky part was to figure out a way to not fall into a forever loop when updating a document with a trigger that runs on every update.

AFAIK there isn’t a “this update was triggered by a trigger”-flag so you need to figure out the bail out condition manually. The following tigger function runs on insert, update and replace. It updates ‘lastModifiedAt’ field in a document to current timestamp using $currentDate operator but only if lastModifiedAt field is not updated in the changeEvent.

exports = function(changeEvent) {
  const docId = changeEvent.documentKey._id;
  const description = changeEvent.updateDescription;
  const insertEvent = changeEvent.operationType === "insert";

  if (!docId || (!insertEvent && !description)) { return; }
  
  if (insertEvent || !Object.keys(description.updatedFields).includes("lastModifiedAt")) {
    context.services
      .get("my-cluster-name")
      .db("my-db-name")
      .collection("my-collection-name")
      .updateOne(
        { _id: docId },
        { $currentDate: {lastModifiedAt: true} }
    );
  }
};

Adding automated created_at field is much simpler; just run the trigger with insert events and you’re set. (Or, you can retrieve the same data without any extra work by using ObjectId.getTimestamp. I always like to have a dedicated field for this, hence a trigger.)