$\textbf{MongoDB and Node}$
$\text{Written by:} $
$\text{Jacob Levine, Class of 2020}$
$\text{Last Revised: March 2020}$
MongoDB is NoSQL database software that uses a document structure similar to JSON, so it's often used on Node.js websites. Due to its powerful searching and high scalibility, Mongo is used on some of the largest applications, such as Google and Uber. It's somewhat controversial for some decisions that prioritize speed over stability.
In Mongo, database entries are called "documents" and work very similarly to JS objects. These documents are stored in "collections" which are grouped into "databases". All documents have a unique "_id" property that is assigned either before (i.e. during object creation) or on insertion. For this project, we're going to use the MongoClient module. The async function below connects to the database in the connection string (in this case the 2022 scouting DB, also note that connection strings will often be stored in enviroment variables instead of source code to avoid revealing them) and pulls out a db (we'll be using the intro-to-node db later to demonstrate reading and writing).
const MongoClient = require('mongodb').MongoClient;
let db;
let getDB = async (dbname) => {
try {
const client = await MongoClient.connect("mongodb+srv://intro-to-node:epycepoch2019@2022-scouting-4vfuu.mongodb.net/test");
db = client.db(dbname);
} catch (err) {
console.error(err);
}
return db;
};
getCollection = async (db,coll) => {
let loc = await getDB(db)
return loc.collection(coll)
};
find
and findOne
¶findOne
returns the first object that the database finds that matches the selector (a collection of properties that we're looking for). find
goes through and returns a cursor of all of the elements that match the selector. This cursor can be for
-looped through (or .forEach
ed) or be casted to an array, like in the function below
findOne = async(db,coll,selector) => {
let loc = await getCollection(db,coll)
return loc.findOne(selector)
};
findOne("intro-to-node", "col1", {"match":10, "team_scouted" : 111})
find = async(db,coll,selector) => {
let loc = await getCollection(db,coll)
return loc.find(selector).toArray() //find returns a cursor, but can cast to Array
};
find("intro-to-node", "col1", {"match":10})
insertMany
and insertOne
¶insertOne
adds one object as a document to a collection while insertMany
adds each element of an array of objects as its own document. Note that if you specify an _id
property to a document, any of the insert properties will fail if your database already has a document with the same _id
(we'll talk about updating documents below...)
insertOne = async(db,coll,info) => {
let loc = await getCollection(db,coll)
loc.insertOne(info, (err,res) => {
if(err){
console.error(err)
}else{
console.log("Number of documents inserted: " + res.insertedCount );
}
})
};
insertOne("intro-to-node","col1", {"match": -1, data: "Michael is lame"})
insert = async(db,coll,info) => {
let loc = await getCollection(db,coll)
loc.insertMany(info, (err,res) => {
if(err){
console.error(err)
}else{
console.log("Number of documents inserted: " + res.insertedCount);
}
});
};
insert("intro-to-node","col1", [{"match" : -2, "class" : "node intro"}, {"match" : -1, "MichaelIsLame" : true}])
remove
¶The remove
function removes all documents that match a selector. There is no removeOne
function, however, you can get it to remove only the first document it finds by setting the single
(called justOne
in MongoDB shell and some other languages) property to true
. Below, I have added a callback that returns the number of documents removed:
remove = async(db,coll,selector) => {
let loc = await getCollection(db,coll)
loc.remove(selector, (err,res) => {
if(err){
console.error(err)
}else{
console.log("Number of documents removed: " + res.result.n);
}
});
};
remove("intro-to-node", "col1", {"match" : -1})
removeOne = async(db,coll,selector) => {
let loc = await getCollection(db,coll)
loc.remove(selector, {"single":true}, (err,res) => {
if(err){
console.error(err)
}else{
console.log("Number of documents removed: " + res.result.n);
}
});
};
removeOne("intro-to-node", "col1", {"match":-2})
update
and upsert
¶updateOne
replaces one value that matches the selector with a replacement object. It throws an error if no documents match the selector, however, this behavior can be changed with upsert
. Setting upsert
to true
makes the db add a new document if there is nothing to replace.
updateOne = async(db,coll,selector,replacement) => { let loc = await getCollection(db,coll) loc.update(selector, replacement); };
updateOne = async(db,coll,selector,replacement) => {
let loc = await getCollection(db,coll)
loc.update(selector, replacement);
};
updateOne("intro-to-node", "col1", {"match" : -2}, {"match" : -2, "class" : "introToNode"})
updateOnewithUpsert = async(db,coll,selector,replacement) => {
let loc = await getCollection(db,coll)
loc.update(selector, replacement, {"upsert": true});
};
updateOnewithUpsert("intro-to-node", "col1", {"match" : -3}, {"match" : -3, "node-mongo":true})
Mongo has a complex system of selectors and regex searches to find documents. We're not going to cover all of them, and I would reccomend that you check out the documentation for more, but here are some of the most common ones:
$lt
- less than$gt
- greater than$lte
- less than or equal to$gte
- greater than or equal to$eq
- equal to$gt
- not equal to** for example, to get all data for matches after 75:
find("intro-to-node", "col1", {"match" : {"$gt" : 75}})
$elemMatch
- at least one element matches the query$size
- array is a certain sizeupdateOnewithUpsert("intro-to-node","col1", {"names" : ["Jacob Levine", "Dev Singh", "Arthur Lu", "Ian Fowler"]},{"names" : ["Jacob Levine", "Dev Singh", "Arthur Lu", "Ian Fowler"]})
findOne("intro-to-node","col1", {"names" : {"$elemMatch":{"$eq":"Jacob Levine"}}})
$and
- only if all querys are true$or
- at least one query is true$not
- only if query isn't true$nor
- only if all queries are not truefind("intro-to-node", "col1", {"$and":[{"team_scouted": 2022}, {"match" : {"$lte" : 20}}]})