Getting the mongo driver

We will use the official MongoDB driver for these parts of the class. Your book also covers Mongoose, which lets you automatically create models and queries similar to a traditional Object-Relational Mapper in SQL projects.

Install the drivers with: npm install --save mongodb

Getting connected to mongo from a node program

There are good tutorials on using the MongoDB client on their website: Aggregation Tutorial

Here’s a 'simple' program that connects to Mongo, inserts documents, runs a query, and cleans itself up.

Warning
This program is not a good program. It is an illustration of why you need to break functionality up into multiple functions.
badBegginner.js
var MongoClient = require('mongodb').MongoClient;

// Connection URL
var url = 'mongodb://localhost:27017';

// Database Name
var dbName = 'test';

// Use connect method to connect to the server
MongoClient.connect(url, { useNewUrlParser: true }, function(err, client) {
	if (err) {
		console.log('Error connecting to Mongo');
		return;
	}
	console.log('Connected successfully to server');

	var db = client.db(dbName);
	var coll = db.collection('myCollection');

	// Use db to issue commands!
	// Most of these commands should be similar to the Mongo console

	coll.insertMany([
		{ 'author' : 'dave', 'score' : 80, 'views' : 100 },
		{ 'author' : 'dave', 'score' : 85, 'views' : 521 },
		{ 'author' : 'ahn', 'score' : 60, 'views' : 1000 },
		{ 'author' : 'li', 'score' : 55, 'views' : 5000 },
		{ 'author' : 'annT', 'score' : 60, 'views' : 50 },
		{ 'author' : 'li', 'score' : 94, 'views' : 999 },
		{ 'author' : 'ty', 'score' : 95, 'views' : 1000 }
	], function(err) {
		if (err) return;
		// Documents are inserted, now we can run a query
		coll.aggregate(
			{$project: {
				_id: 0,
				author: 1,
				value: {$multiply: [{$add: ['$score', '$views']}, 0.8]}
			}}, function(err, cursor) {
				if (err) return;
				cursor.toArray(function(err, documents) {
					if (err) return;
					console.table(documents);
					coll.deleteMany(function(err) {
						if (err) return;
						client.close();
					});
				});
			});
	});

});

Let’s rewrite the previous program into multiple functions.

betterBeginner.js
var MongoClient = require('mongodb').MongoClient;

// Connection URL
var url = 'mongodb://localhost:27017';

// Database Name
var dbName = 'test';

function connect(dbURL, dbName, cb) {
	MongoClient.connect(url, { useNewUrlParser: true }, function(err, client) {
		if (err) {
			console.log('Error connecting to Mongo\n', err);
			return cb(err);
		}
		console.log('Connected successfully to server');

		var db = client.db(dbName);

		cb(null, client, db);
	});
}

function insertData(coll, cb) {
	// Use db to issue commands!
	// Most of these commands should be similar to the Mongo console

	coll.insertMany([
		{ 'author' : 'dave', 'score' : 80, 'views' : 100 },
		{ 'author' : 'dave', 'score' : 85, 'views' : 521 },
		{ 'author' : 'ahn', 'score' : 60, 'views' : 1000 },
		{ 'author' : 'li', 'score' : 55, 'views' : 5000 },
		{ 'author' : 'annT', 'score' : 60, 'views' : 50 },
		{ 'author' : 'li', 'score' : 94, 'views' : 999 },
		{ 'author' : 'ty', 'score' : 95, 'views' : 1000 }
	], function(err, results) {
		cb(err, coll, results);
	});
}

function getScore(coll, cb) {
	coll.aggregate(
		[{$project: {
			_id: 0,
			author: 1,
			value: {$multiply: [{$add: ['$score', '$views']}, 0.8]}
		}}], function(err, cursor) {
			if (err) cb(err);

			console.dir(cursor);

			cursor.toArray(function(err, documents) {
				if (err) cb(err);
				cb(null, documents);
			});
		});
}

function cleanupData(coll, cb) {
	coll.deleteMany(function(err) {
		cb(err);
	});
}

connect(url, dbName, function (err, client, db) {
	var coll = db.collection('myCollection');

	insertData(coll, function(err) {
		if (err) {
			console.log(err);
			client.close();
			return;
		}
		getScore(coll, function(err, documents) {
			console.table(documents);
			cleanupData(coll, function(err) {
				if (err) {
					console.log(err);
				}
				client.close();
			});
		});
	});
});

REST Design

What data do we need to store for an assignment?

What data do we need to store for a quiz?

What data do we need to store for a student?

Towards a 'User' data model

Note
There are many ways to do this, and this may or may not be a good way. We were exploring ways of writing this code in class.
lib/models/Users.js
var _ = require('lodash');
var bcrypt = require('bcrypt');
var ObjectId = require('mongodb').ObjectId;

var collection = 'users';

function Users(db, data) {
	this.coll = db.collection(collection);

	this.data = {};

	// we don't really want to represent _id, but we can use it to see if we hit the DB
	if (data._id)
		this._id = data._id;

	this.data.uid = String(data.uid) || undefined;
	this.data.name = data.name || 'fool';
	this.data.role = data.role || 'student';
	this.data.DOB = data.DOB || new Date('1972/01/01');
	this.data.passwordHash = data.passwordHash || undefined;
	this.data.enrollments = data.enrollments || [];
}

// All instance methods
Users.prototype = {

	setPassword: function(password) {
		this.data.passwordHash = bcrypt.hashSync(password, 10);
	},

	checkPassword: function(password) {
		if (!this.data.passwordHash) return false;
		return (bcrypt.compareSync(password, this.data.passwordHash));
	},

	// Hydrate a Users object if given a UID
	get: function(callback) {
		var self = this;
		if (!self.data.uid) {
			return callback('No UID provided');
		}
		self.coll.aggregate([
			{$match:{
				uid: self.data.uid
			}}
		],
		function(err, cursor) {
			if (err) return callback(err);

			cursor.toArray(function(err, documents) {
				if (err) return callback(err);
				if (documents.length !== 1) return callback('Got != one user!' + documents.length);

				self.data = documents[0];
				self._id = self.data._id;
				self.data._id = undefined;

				return callback(null, self);
			});
		});
	},

	// Save a User to the database
	save: function(callback) {
		var self = this;
		var errs = [];
		var data = this.data;
		delete data._id; // cannot update the _id field!

		// Should perform verification of all fields and return callback(error) if something fails.
		if (!data.uid)
			errs.push('User does not have a uid');
		if (!(data.DOB instanceof Date))
			errs.push('DOB is not a date');
		if (data.role != 'student' && data.role != 'instructor')
			errs.push('role must be one of [\'student\', \'instructor\']');

		if (errs.length > 0)
			return callback(errs);

		self.coll.find({
					uid: data.uid
			}, function(err, cursor) {
				cursor.count(function(err, count) {
					if (count > 0) {
						self.coll.updateOne({uid: data.uid},
							{$set: data}, function(err, result) {
								return callback(err);
							});
					} else {
						self.coll.insertOne(data, function(err, result) {
								return callback(err);
							});
					}
				});
			});
	},

	delete: function(callback) {
		var uid = this.data.uid;
		if (!uid) return callback('No UID given.');
		this.coll.deleteOne({uid: uid}, callback);
	},

	toString: function() {
		return JSON.stringify(this.data, 0, 2);
	},

	valueOf: function() {
		return this.data;
	}
};

// Here's a custom query. It's a static method!
Users.all = function(db, options, callback)  {
	var coll = db.collection(collection);
	if (!callback) {
		callback = options;
		options = {};
	}

	var pipeline = [];

	if (options.role && (options.role === 'student' || options.role === 'instructor')) {
		pipeline.push(
			{$match:{
				role: options.role
			}});
	}

	coll.aggregate(pipeline,
		function(err, cursor) {
			if (err) return callback(err);

			cursor.toArray(function(err, documents) {
				if (err) return callback(err);

				var students = _.map(documents, function(doc) {
					return new Users(db, doc);
				});

				return callback(null, students);
			});
		});
};

module.exports = Users;

Not previously mentioned: Update/Delete

We can use deleteOne and deleteMany to remove documents. They take a match expression (just like $match bodies) to match which documents will be removed.

deleteOneDeleteMany.js
db.collection('inventory').deleteMany({ status: 'A' }); // deletes all documents matching a status of 'A'

We can also update documents using the updateOne and updateMany command. This command requires a match expression and an update operator to mutate the document. You can read more here: Update Documents.

update.js
db.collection('inventory').updateOne(
  { item: 'paper' },
  {
    $set: { 'size.uom': 'cm', status: 'P' },
    $currentDate: { lastModified: true }
  }
);