Storing notes in MongoDB

MongoDB is widely used with Node.js applications, a sign of which is the popular MEAN acronym: MongoDB (or MySQL), Express, Angular, and Node.js. MongoDB is one of the leading NoSQL databases, meaning it is a database engine that does not use SQL queries. It is described as a scalable, high-performance, open source, document- oriented database. It uses JSON-style documents with no predefined, rigid schema and a large number of advanced features. You can visit their website for more information and documentation at http://www.mongodb.org.

Mongoose is a popular ORM for MongoDB (http://mongoosejs.com/). In this section, we’ll use the native MongoDB driver instead, but Mongoose is a worthy alternative.

First, you will need a running MongoDB instance. The Compose- (https://www.compose.io/) and ScaleGrid- (https://scalegrid.io/) hosted service providers offer hosted MongoDB services. Nowadays, it is straightforward to host MongoDB as a Docker container as part of a system built of other Docker containers. We’ll do this in Chapter 13, Unit Testing and Functional Testing.

It’s possible to set up a temporary MongoDB instance for testing on, say, your laptop. It is available in all the operating system package management systems, or you can download a compiled package from mongodb.com. The MongoDB website also has instructions (https://docs.mongodb.org/manual/installation/).

For Windows, it may be most expedient to use a cloud-hosted MongoDB instance.

Once installed, it’s not necessary to set up MongoDB as a background service. Instead, you can run a couple of simple commands to get a MongoDB instance running in the foreground of a command window, which you can kill and restart any time you like.

In a command window, run the following:

$ mkdir data

$ mongod –dbpath data 

This creates a data directory and then runs the MongoDB daemon against the directory.

In another command window, you can test it as follows:

$ mongo

MongoDB shell version v4.2.2 connecting to:

mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mong odb

Implicit session: session { “id” : UUID(“308e285e-5496-43c5-81ca- b04784927734”) }

MongoDB server version: 4.2.2

> foo.save({ a: 1});

WriteResult({ “nInserted” : 1 })

> foo.find();

{ “_id” : ObjectId(“5e261ffc7e36ca9ed76d9552”), “a” : 1 }

> 

bye 

This runs the Mongo client program with which you can run commands. The command language used here is JavaScript, which is comfortable for us.

This saves a document in the collection named foo. The second command finds all documents in foo, printing them out for you. There is only one document, the one we just inserted, so that’s what gets printed. The _id field is added by MongoDB and serves as a document identifier.

This setup is useful for testing and debugging. For a real deployment, your MongoDB server must be properly installed on a server. See the MongoDB documentation for these instructions.

With a working MongoDB installation in our hands, let’s get started with implementing the MongoNotesStore class.

1. A MongoDB model for the Notes application

The official Node.js MongoDB driver (https://www.npmjs.com/package/mongodb) is created by the MongoDB team. It is very easy to use, as we will see, and its installation is as simple as running the following command:

$ npm install mongodb@3.x –save 

This sets us up with the driver package and adds it to package.json.

Now, create a new file, models/notes-mongodb.mjs:

import { Note, AbstractNotesStore } from ‘./Notes.mjs’;

import mongodb from ‘mongodb’;

const MongoClient = mongodb.MongoClient;

import DBG from ‘debug’;

const debug = DBG(‘notes:notes-mongodb’);

const error = DBG(‘notes:error-mongodb’);

let client;

const connectDB = async () => {

if (!client) client = await

MongoClient.connect(process.env.MONGO_URL);

}

const db = () => { return client.db(process.env.MONGO_DBNAME); };

This sets up the required imports, as well as the functions to manage a connection with the MongoDB database.

The MongoClient class is used to connect with a MongoDB instance. The required URL, which will be specified through an environment variable, uses a straightforward format: mongodb://localhost/. The database name is specified via another environment variable.

The connectDB function creates the database client object. This object is only created as needed. The connection URL is provided through the MONGO_URL environment variable.

The db function is a simple wrapper around the client object to access the database that is used for the Notes application, which we specify via the MONGO_DBNAME environment variable. Therefore, to access the database, the code will have to call db().mongoDbFunction().

Now, we can implement the MongoDBNotesStore class:

export default class MongoDBNotesStore extends AbstractNotesStore {

async close() {

if (client) client.close();

client = undefined;

}

async update(key, title, body) {

await connectDB();

const note = new Note(key, title, body);

const collection = db().collection(‘notes’);

await collection.updateOne({ notekey: key },

{ $set: { title: title, body: body } });

return note;

}

async create(key, title, body) {

await connectDB();

const note = new Note(key, title, body);

const collection = db().collection(‘notes’);

await collection.insertOne({

notekey: key, title: title, body: body

});

return note;

}

async read(key) {

await connectDB();

const collection = db().collection(‘notes’);

const doc = await collection.findOne({ notekey: key });

const note = new Note(doc.notekey, doc.title, doc.body);

return note;

}

async destroy(key) {

await connectDB();

const collection = db().collection(‘notes’);

const doc = await collection.findOne({ notekey: key });

if (!doc) {

throw new Error(‘No note found for ${key}’);

} else {

await collection.findOneAndDelete({ notekey: key });

this.emitDestroyed(key);

}

}

async keylist() {

await connectDB();

const collection = db().collection(‘notes’);

const keyz = await new Promise((resolve, reject) => {

const keyz = [];

collection.find({}).forEach(

note => { keyz.push(note.notekey); }, err => {

if (err) reject(err);

else resolve(keyz);

}

);

});

return keyz;

}

async count() {

await connectDB();

const collection = db().collection(‘notes’);

const count = await collection.count({});

return count;

}

}

MongoDB stores all documents in collections. A collection is a group of related documents and is analogous to a table in a relational database. This means creating a new document or updating an existing one starts by constructing it as a JavaScript object and then asking MongoDB to save the object to the database. MongoDB automatically encodes the object into its internal representation.

The db().collection method gives us a Collection object with which we can manipulate the named collection. In this case, we access the notes collection with db().collection(‘notes’).

In the create method, we use insertOne; as the method name implies, it inserts one document into the collection. This document is used for the fields of the Note class.

Likewise, in the update method, the updateOne method first finds a document (in this case, by looking up the document with the matching notekey field) and then changes fields in the document, as specified, before saving the modified document back to the database.

The read method uses db().findOne to search for the note.

The findOne method takes what is called a query selector. In this case, we are requesting a match against the notekey field. MongoDB supports a comprehensive set of operators for query selectors.

On the other hand, the updateOne method takes what is called a query filter. As an update operation, it searches the database for a record that matches the filter, updates its fields based on the update descriptor, and then saves it back to the database.

MongoDB has many variations of base operations. For example, findOne is a variation on the basic find method.

In our destroy method, we see another find variant, findOneAndDelete. As the name implies, it finds a document that matches the query descriptor and then deletes the document.

In the keylist method, we need to process every document in the collection, and so the find query selector is empty. The find operation returns a Cursor, which is an object used to navigate query results. The Cursor.forEach method takes two callbacks and is not a Promise-friendly operation, so we have to use a Promise wrapper. The first callback is called for every document in the query result, and in this case, we simply push the notekey field into an array. The second callback is called when the operation is finished, and we notify the Promise whether it succeeded or failed. This gives us our array of keys, which is returned to the caller.

In our count method, we simply call the MongoDB count method. The count method takes a query descriptor and, as the name implies, counts the number of documents that match the query. Since we’ve given an empty query selector, it ends up counting the entire collection.

This allows us to run Notes with NOTES_MODEL set to mongodb to use a MongoDB database.

Now that we have everything coded for MongoDB, we can proceed with testing Notes.

2. Running the Notes application with MongoDB

We are ready to test Notes using a MongoDB database. By now, you know the drill; add the following to the scripts section of package.json:

“mongodb-start”: “cross-env DEBUG=notes:* MONGO_URL=mongodb://localhost/ MONGO_DBNAME=chap07 NOTES_MODEL=mongodb node ./app.mjs”,

“mongodb-server1”: “cross-env DEBUG=notes:* MONGO_URL=mongodb://localhost/ MONGO_DBNAME=chap07 NOTES_MODEL=mongodb PORT=3001 node ./app.mjs”,

“mongodb-server2”: “cross-env DEBUG=notes:* MONGO_URL=mongodb://localhost/ MONGO_DBNAME=chap07 NOTES_MODEL=mongodb PORT=3002 node ./app.mjs”,

The MONGO_URL environment variable is the URL to connect with your MongoDB database. This URL is the one that you need to use to run MongoDB on your laptop, as outlined at the top of this section. If you have a MongoDB server somewhere else, you’ll be provided with the relevant URL to use.

You can start the Notes application as follows:

$ npm run mongodb-start

> notes@0.0.0 mongodb-start /home/david/Chapter07/notes

> cross-env DEBUG=notes:* MONGO_URL=mongodb://localhost/

MONGO_DBNAME=chap07 NOTES_MODEL=mongodb node ./app.mjs 

notes:debug Listening on port 3000 +0ms 

The MONGO_URL environment variable should contain the URL for connecting with your MongoDB database. The URL shown here is correct for a MongoDB server started on the local machine, as would be the case if you started MongoDB at the command line as shown at the beginning of this section. Otherwise, if you have a MongoDB server provisioned somewhere else, you will have been told what the access URL is, and your MONGO_URL variable should have that URL.

You can start two instances of the Notes application and see that both share the same set of notes.

We can verify that the MongoDB database ends up with the correct value. First, start the MongoDB client program as so:

$ mongo chap07

MongoDB shell version v3.6.8

connecting to: mongodb://127.0.0.1:27017/chap07 

Again, this is assuming the MongoDB configuration presented so far, and if your configuration differs then add the URL on the command line. This starts the interactive MongoDB shell, connected to the database configured for use by Notes. To inspect the content of the database, simply enter the command: db.notes.find(). That will print out every database entry.

With that, we have completed support not only for MongoDB but also for several other databases in the Notes application, and so we are now ready to wrap up the chapter.

Source: Herron David (2020), Node.js Web Development: Server-side web development made easy with Node 14 using practical examples, Packt Publishing.

Leave a Reply

Your email address will not be published. Required fields are marked *