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. |
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.
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. |
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.
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.
db.collection('inventory').updateOne(
{ item: 'paper' },
{
$set: { 'size.uom': 'cm', status: 'P' },
$currentDate: { lastModified: true }
}
);