1. Could we have just added the _id to the passed-in object, and returned that instead of doing a find() for the inserted object?
2. We could have used insert() instead of insertOne(). What would be the difference? Why did we choose to use the latter?
hint: Look up the MongoDB driver documentation.
answers are available at the end of the chapter.
Summary
In this chapter, we looked at the installation and use of MongoDB, both via the mongo shell as well as from Node.js code via the native driver. We explored a few methods, the pillars of MongoDB via the shell as well as a test program in Node.js. We then used these learnings to insert and read a document from the database, thus making the issue list persistent.
You should now have a good understanding of the fundamentals of MongoDB and the paradigms that it uses. We did not go deeper into other operations such as update and delete, or getting summary reports via aggregates. We’ll do all of that later, when we implement features in the Issue Tracker that require them to be used.
In the next chapter, we’ll take a break from implementing features. Instead, we’ll start getting organized. We’ll modularize the code and use tools to improve our productivity since the project is starting to get bigger.
Chapter 6 ■ Using MongoDB
Answers to Exercises
Exercise: Mongo Shell
1. Yes, the mongo shell does support some ES2015 features.
Notably, you can use arrow functions, string interpolation, and const variables.
2. This can be done using the $exists operator like this:
> db.employees.find({"name.middle": {$exists: true}}) 3. The filter specification is not a JSON document, because it
is not a string. It is a regular JavaScript object, which is why you are able to skip the quotes around the property names.
You will also be able to have real Date objects as field values, unlike a JSON string.
4. The $unset operator in an update can be used to unset a field (which is actually different from setting it to null). Here is an example:
> db.employees.update(({_id: ObjectId("57b1caea3475bb1784747ccb")}, {"name.middle": {$unset: null}})
Although we supplied null as the value for $unset, this value is ignored. It can be anything.
5. The 1 indicates an ascending sort order for traversing the index.
-1 is used to indicate a descending sort order. This is useful only for compound indexes, because a simple index on one field can be used to traverse the collection in either direction.
Exercise: Schema Initialization
1. Creating our own _id would make the display and
identification of the issue more user friendly, because it can be a continuous number like 1, 2, 3, and so on. The ObjectId is harder to refer to. On the other hand, generating a continuous number is more work, and may not scale well when there are a large number of documents and simultaneous inserts from multiple clients. Read the MongoDB tutorial called Create an Auto-Incrementing Sequence Field for a more detailed discussion.
2. A search bar is quite helpful when searching for issues. A text index (an index based on the words) on the title field would be useful in this case. Find out more about text indexes in the MongoDB tutorial. We’ll implement a text index towards the end of the book.
Chapter 6 ■ Using MongoDB
113
Exercise: Reading from MongoDB
1. The connection object is in fact a connection pool. It automatically figures out the best thing to do: reuse an existing TCP connection, reestablish a new connection when the connection is broken, etc. Using a global variable (at least, reusing the connection object) is the recommended usage. If you don’t like global variables, you can create a module and encapsulate the connection within it’s namespace.
2. If the database is unavailable for a short period (30 seconds), the driver waits and reconnects when the database is available again. If the database is unavailable for a longer period, the read throws an error. The driver is also unable to reestablish a connection when the database is restored. The application server needs to be restarted in this case.
If you’d rather throw an error if the database is unreachable, set bufferMaxEntries to 0 when creating the connection. This is perhaps the preferred approach for a single server. When you have a replica set, the default behavior works better.
3. One option is to use limit() on the result to limit the return value to a maximum number of records. For example, find().limit(100) returns the first 100 documents. If you were to paginate the output in the UI, you could also use the skip() method to specify where to start the list.
Another option is to use forEach() or stream() to stream the result back to the client, without occupying too much memory on the server to hold the entire array. This assumes that the client can handle large results.
Exercise: Writing to MongoDB
1. Adding the _id and returning the object passed in would have worked, so long as you know for a fact that the write was a success and the object was written to the database as is. In most cases, this would be true, but it’s a good practice to get the results from the database, as that is the ultimate truth.
Since we are querying on the _id (which is automatically indexed), the extra call is very minimal in terms of latency added to the query.
2. The result from an insert() is slightly different from insertOne(). Instead of insertedId, it returns an array of insertedIds. You use insertOne() since insert() is deprecated in favor of insertOne() and insertMany(), and the usage is unambiguous.