×
By the end of this chapter, you should be able to:
So far we have been using an array to store our data. Since this array is being stored in memory on the server, it only lasts as long as our server is running. The second our server goes down, we lose all our data! This simply will not work for any practical application. At some point, we will need to be able to save, or persist, our data to a hard drive. To do that, we'll need to use a database.
The database we will be using is MongoDB, which is a NoSQL database. To understand what that means, however, we first need to understand what a SQL database is. SQL databases store tables of data; and inside each table, the rows (also called records) are structured based on a predefined schema.
What does this mean? Let's consider a simple example. Suppose you have a simple blogging application, where users can log in and write posts. In a SQL database, this might mean you have two tables:
Furthermore, the tables in SQL databases are called "relational" because they are designed to cross-reference each other. The term RDBMS refers to "Relational DataBase Management System", examples of which are: MySQL or PostgreSQL. For a comprehensive introduction to SQL, visit our curriculum on SQL in the Flask Fundamentals section.
In contrast with SQL databases which have relational, schema-based tables, NoSQL databases are non-relational and many do not store data in table form. For a great overview of SQL vs. NoSQL, click here.
MongoDB is a very popular NoSQL database engine. It stores collections of schemaless documents in BSON form (essentially binary JSON). MongoDB query syntax is very similar to JavaScript and can easily be converted to pure JS. Because MongoDB stores JSON-like documents and queries them with JavaScript, it is a natural choice for a database when developing with Node or Express. For a more complete introduction, head here.
Depending on your OS - here are instructions to install MongoDB.
Now that we have MongoDB installed let's review some essential terms:
database - this is where all of our data is stored. We can create multiple databases, and each database will store multiple collections.
collection - collections are aggregations of documents. Each document can have any number of attributes because collections do not enforce a schema.
document - documents are JSON-like objects that contain all types of information. They can have any valid JSON data type (e.g. String, Number, Array, Object).
In the language of MongoDB, we wouldn't say that we have a table of data on users, where each row corresponds to one user. Instead we'd say that we have a collection of user documents, where each document corresponds to one user.
Remember these user documents do not need to have a common structure: one user could have an email address and a first name, another could have a last name and an address, and a third could have an entirely different set of data being stored.
To perform database operations in mongo, start up a mongo server by typing mongod
in the terminal (unless you started the mongodb service in the background using homebrew, for instance).
Then open up a new tab and enter the MongoDB shell by typing mongo
.
Here are some essential database commands:
show dbs
will display a list of all available databases.use test
lets you switch to a database called test
. If that database does not exist, it will be created by this command.db.getCollectionNames()
lists collections that have already been createddb.dropDatabase()
deletes the current database and all its collections.In MongoDB, collections are created on-the-fly as soon as you insert documents into them, which we'll get into below!
Here is a list of the most commonly-used CRUD operations in MongoDB with examples. For a full list, check out the MongoDB docs here.
To create in mongo we use the insert
function, which belongs to a collection we are operating on. insert
accepts an object of keys and values.
You can also insert an array of objects using the insertMany
function.
db.users.insert({ first: "Elie", last: "Schoppik", isInstructor: true, favoriteColors: ["Green", "Blue", "Purple"], favoriteFood: "sushi" }); db.users.insert({ first: "Mary", last: "Malarkin", isInstructor: false, favoriteColors: ["Green", "Yellow"], hometown: "Omaha" }); db.users.insertMany([ { first: "Tim", funFact: "owns a boat!" }, { first: "Matt", funFact: "has a pet dog!" } ]);
To query documents in mongo, we use the find
command to find multiple objects and findOne
for a single one.
db.users.find();
/*
{ "_id" : ObjectId("5a288cc40189b2bedb2967f9"), "first" : "Mary", "last" : "Malarkin", "isInstructor" : false, "favoriteColors" : [ "Green", "Yellow" ], "hometown" : "Omaha" }
{ "_id" : ObjectId("5a288de60189b2bedb2967fb"), "first" : "Elie", "last" : "Schoppik", "isInstructor" : true, "favoriteColors" : [ "Green", "Blue", "Purple" ], "favoriteFood" : "sushi" }
{ "_id" : ObjectId("5a288e2b0189b2bedb2967fc"), "first" : "tim", "funFact" : "owns a boat!" }
{ "_id" : ObjectId("5a288e2b0189b2bedb2967fd"), "first" : "matt", "funFact" : "has a pet dog!" }
*/
The first argument to find/findOne is an object that contains a key-value query. For example, if you want to query users whose first name is "Mary" you would type:
db.users.findOne({ first: "Mary" }); /*{ "_id": ObjectId("58aa39cb62a53c60f58471c7"), "first": "Mary", "last": "Malarkin", "isInstructor": false, "favoriteColors": [ "Green", "Yellow" ], "hometown": "Omaha" } */
Note: if there are multiple matches, findOne
returns the first entry (usually first inserted).
The second argument to find/findOne is an object that contains which fields to return, in case you don't need all of the fields. For example, if you just wanted to return Elie's favorite food, your query would look like this:
db.users.findOne( // query by first and last name { first: "Elie", last: "Schoppik" }, // give us only the "favoriteFood" field { favoriteFood: 1 } ); /* { "_id" : ObjectId("5a288de60189b2bedb2967fb"), "favoriteFood" : "sushi" } */
Note: _id
is always returned by default.
Tip: you can use the .pretty()
method to return a nicer-looking list of documents. Here is an example query for users who have "Green" as a favorite color:
db.users.find({ favoriteColors: "Green" }).pretty(); /* { "_id" : ObjectId("5a288cc40189b2bedb2967f9"), "first" : "Mary", "last" : "Malarkin", "isInstructor" : false, "favoriteColors" : [ "Green", "Yellow" ], "hometown" : "Omaha" } { "_id" : ObjectId("5a288de60189b2bedb2967fb"), "first" : "Elie", "last" : "Schoppik", "isInstructor" : true, "favoriteColors" : [ "Green", "Blue", "Purple" ], "favoriteFood" : "sushi" } */
To update documents in mongo, we use the update
method. The first argument is the query to select the appropriate documents; the second argument is the actual keys and values to update:
db.users.update( // what to find the user by { first: "Tim" }, // what data to update { first: "Michael", moreInfo: "Is a new instructor", favoriteNumbers: [11, 111, 1111] } ); /* WriteResult({ "nMatched": 1, "nUpserted": 0, "nModified": 1 }) */
We can also use update
to add a new document, if we pass it the upsert
option! upsert
is a portmanteau of update
and insert
. When you upsert
into a collection, a document will be updated if it is found, and inserted if no matching document is found.
db.users.update( { name: "fjlsadkdfjsdaklfjdklsajfklds" }, { name: "Upsert will insert if it can not find!", moreInfo: "How cool is that?", favorite_numbers: [11, 12, 13] }, // if it is not found, insert it! (upsert = update or insert) { upsert: true } ); /* WriteResult({ "nMatched": 0, "nUpserted": 1, "nModified": 0, "_id": ObjectId("58aa3a859c4f01de70dcd04b") }) */
Finally, if you want to update multiple documents, you can set multi
equal to true
.
// update every single one that is found using multi:true db.users.update( // find all where the name is not equal to 'nope' { name: { $ne: "nope" } }, // set a new key of isHilarious to true { $set: { isHilarious: true } }, // for more than 1 record { multi: true } ); /* WriteResult({ "nMatched": 4, "nUpserted": 0, "nModified": 4 }) */
For more on update, check out the docs.
To delete documents in mongo, we use the remove
method.
// remove a single user db.users.remove({ name: "Bob" }); /* Removed 0 record(s) in 1ms WriteResult({ "nRemoved": 1 }) */ // remove all the users db.users.remove({}); /* Removed 1 record(s) in 2ms WriteResult({ "nRemoved": 3 }) */
Since the process of deleting or updating involves first finding a record, we can use some built-in methods to perform both operations at once.
db.users.findAndModify({ // find someone with a name of elie and a score greater than 10 query: { name: "Elie", score: { $gt: 10 } }, // increment their score by 1 update: { $inc: { score: 1 } } }); db.users.findOneAndUpdate({ name: "Elie" }, { $inc: { points: 5 } }); db.users.findOneAndDelete({ name: "Elie" });
You can read more about the difference between modify and update here. And for documentation on all of these methods (and others we haven't discussed), go here.
Here is a screencast to get up and running with MongoDB:
When you're ready, move on to MongoDB with Mongoose