Connect to MongoDB with monk in Node.js

An example of connecting to mongodb with the Monk NPM module.

monk’s github repo description says it all

The wise MongoDB API

A tiny layer that provides simple yet substantial usability improvements for MongoDB usage within Node.JS.

I love the super simple api

const db = require('monk')('localhost/db')
const users = db.get('users')

Use it in production 💯

Below you can see a real-world snippet of the db connection for pomodoro.cc (source code here).

The file lib/db.js

const monk = require('monk')
const logger = require('pino')()

logger.info('process.env.NODE_ENV', process.env.NODE_ENV)
logger.info('MONGO_URL set?', !!process.env.MONGO_URL)
module.exports = monk(process.env.MONGO_URL)

Nothing more, nothing less.

You could use it then to create your models and repositories around it:

For example lib/models/users.js:

const db = require('../db')
const users = db.get('users')

users.createIndex({ _id: 1 })
users.createIndex({ createdAt: 1 })

module.exports = users

use cases

stream a collection

In pomodoro.cc I use this feature to stream documents from the users collection, to update a users twitter avatar.

Here you can find the full code snippet:

await users.find({
  twitterAvatarNotFound: { $exists: false },
  $or: [{
    twitterAvatarUpdatedAt: { $lt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7) }
  }, {
    twitterAvatarUpdatedAt: { $exists: false }
  }]
})
  .each(async (user, { pause, resume }) => {
    // ... process user twitter avatar
  })
  .catch(err => console.error(err))

Aggregations

Again, as a real-world production use-case, I take pomodoro.cc’s daily analytics aggregation for Pro users.

In this example I want to showcase how a daily aggregate of documents can be done with MongoDB, monk and Node.js.

About aggregations from the official docs:

Aggregation operations process data records and return computed results. Aggregation operations group values from multiple documents together, and can perform a variety of operations on the grouped data to return a single result. MongoDB provides three ways to perform aggregation: the aggregation pipeline, the map-reduce function, and single purpose aggregation methods.

An example from pomodoro.cc source code

  return pomodoros.aggregate(
    [
      {
        $match: {
          userId: monk.id(userId)
        }
      }, {
        $project: {
          doc: '$$ROOT',
          year: { $substr: [`$${field}`, 0, 4] },
          month: { $substr: [`$${field}`, 5, 2] },
          day: { $substr: [`$${field}`, 8, 2] }
        }
      }, {
        $group: {
          _id: {
            year: '$year',
            month: '$month',
            day: '$day'
          },
          docs: {
            $push: '$doc'
          }
        }
      }, {
        $project: {
          _id: 0,
          day: {
            $concat: ['$_id.year', '-', '$_id.month', '-', '$_id.day']
          },
          docs: '$docs'
        }
      }, {
        $sort: {
          day: -1
        }
      }
    ]
  )

Here I aggregated documents of a collection by date, matched by a single userId.

upsertion - update or insert

what an upsert operation is in a few words:

Insert a New Document if No Match Exists

from the official docs you can see that

Optional. If set to true, creates a new document when no document matches the query criteria. The default value is false, which does not insert a new document when no match is found.

It as simple as providing the upsert: true option to the update function:

const result = await books.update(
   { item: "ZZZ135" },   // Query parameter
   {                     // Replacement document
     item: "ZZZ135",
     stock: 5,
     tags: [ "database" ]
   },
   { upsert: true }      // Options
)

The result will look something like this:

{
  "nMatched" : 0,
  "nUpserted" : 1,
  "nModified" : 0,
  "_id" : ObjectId("5da78973835b2f1c75347a83")
}

this gives us more information on what the update operation actually did.


Let me know how you are using monk in production!

Here, have a slice of pizza 🍕