Storing and retrieving data with MongoDB

MongoDB is a NoSQL database offering that maintains a philosophy of performance over features. It's designed for speed and scalability. Instead of working relationally, it implements a document-based model that has no need for schemas (column definitions). The document model works well for scenarios where relationships between data are flexible and where minimal potential data loss is an acceptable cost for the speed enhancements (a blog for instance).

While it's in the NoSQL family, MongoDB attempts to sit between two worlds, providing a syntax reminiscent of SQL but operating non-relationally.

In this task, we'll implement the same quotes database as in the previous recipe, using MongoDB instead of MySQL.

Getting ready

We'll want to run a MongoDB server locally. It can be downloaded from http://www.mongodb.org/downloads.

Let's start the MongoDB service, mongod, in the default debug mode:

mongod --dbpath [a folder for the database]

This allows us to observe the activities mongod as it interacts with our code, if we wanted to start it as a persistent background service we would use.

mongod --fork --logpath [p] --logappend dbpath [p]

Where [p] is our desired path.

Tip

More information on starting and correctly stopping mongod can be found at http://www.mongodb.org/display/DOCS/Starting+and+Stopping+Mongo.

To interact with MongoDB from Node, we'll need to install the mongodb native bindings driver module.

npm install mongodb

We'll also create a new folder for our MongoDB-based project, with a new quotes.js file.

How to do it...

We must require the mongodb driver, initiate a MongoDB server instance, and create a client which loads the quotes database and connects to the MongoDB server.

var mongo = require('mongodb'), 
var server = new mongo.Server("localhost", 27017); 
var client = new mongo.Db('quotes', server);

var params = {author: process.argv[2], quote: process.argv[3]};

Notice we've also inserted our params object for reading user input from the command line.

Now we open a connection to our quotes database and load (or create if necessary) our quotes collection (a table would be the closest similar concept in SQL).

client.open(function (err, client) { 
  if (err) { throw err; } 
  var collection = new mongo.Collection(client, 'quotes'),
  client.close();
});

Next, we'll insert a new document (in SQL terms this would be a record) according to a user-defined author and quote.

We'll also output any quotes by the specified author to the console.

client.open(function (err, client) { 
  if (err) { throw err; } 

  var collection = new mongo.Collection(client, 'quotes'), 

  if (params.author && params.quote) { 
    collection.insert({author: params.author, quote: params.quote}); 
  }

 if (params.author) { 

    collection.find({author: params.author}).each(function (err, doc) { 
      if (err) { throw err; } 
      if (doc) { console.log('%s: %s 
', doc.author, doc.quote); return; } 
      client.close(); 
    }); 

    return; 
  }

client.close();

});

We can see our MongoDB backed quotes application in action in the following screenshot:

How to do it...

How it works...

When we create a new instance of mongo.Db, we pass in the name of the database as the first parameter. MongoDB will intelligently create this database if it doesn't exist.

We use the open method of the Db instance, which we named client, to open a connection to our database. Once the connection is made, our callback function is executed where we can interact with the database via the client parameter.

We start off by making a Collection instance. Collection is similar to an SQL table in that it holds all our database fields. However, rather than field values being grouped by column, a collection contains multiple documents (like records) where each field holds both the field name and its value (documents are very much like JavaScript objects).

If both quote and author are defined, we invoke the insert method of our Collection instance, passing in an object as our document.

Finally, we use find which is comparable to the SELECT SQL command, passing in an object that specifies the author field and desired value. The mongodb driver provides a convenience method (each) that can be chained with the find method. each executes the callback passed to it for each document as it's found. The last loop of each passes in doc as null, which conveniently signals that MongoDB has returned all records.

So as long as doc is truthy, we pass the author and quote properties of every doc found. Once doc is null, we allow the interpreter to discover the last part of the callback, client.close, by not returning early from the callback.

The second and final client.close situated at the end of the client.open callback is invoked only when there are no arguments defined via the command line.

There's more...

Let's check out some other useful MongoDB features.

Indexing and aggregation

Indexing causes MongoDB to create a list of values from a chosen field. Indexed fields accelerate query speeds because a smaller set of data can be used to cross reference and pull from a larger set. We can apply an index to the author field and see performance benefits, especially as our data grows. Additionally, MongoDB has various commands allowing us to aggregate our data. We can group, count, and return distinct values.

Note

For more advanced needs or larger sets of data, map/reduce functions can aggregate. CouchDB also uses map/reduce to generate views (stored queries), See Retrieving data from CouchDB with Cradle.

Let's create and output a list of authors found in our database, and save our code to a file called authors.js.

var mongo = require('mongodb'), 
var server = new mongo.Server("localhost", 27017); 
var client = new mongo.Db('quotes', server); 

client.open(function (err, client) { 
  if (err) { throw err; } 
  var collection = new mongo.Collection(client, 'quotes'),  
  collection.ensureIndex('author', {safe: true}, function (err) { 
    if (err) { throw err; } 
    collection.distinct('author', function (err, result) { 
        if (err) { throw err; } 
        console.log(result.join('
')); 
        client.close(); 
      });  
    }); 
    
});

As usual, we opened up a connection to our quotes database, grabbing our quotes collection. Using ensureIndex creates an index only if one doesn't already exist. We pass in safe:true so that MongoDB returns any errors and our callback works as expected. Inside the callback, we invoke the distinct method on our collection, passing in author. The result is passed as an array through as result. We join the array into a string using new lines and output to the console. .

Updating modifiers, sort, and limit

We could make it possible for a hypothetical user to indicate if they were inspired by a quote (such as a Like button), then we could use the sort and limit commands to output the top ten most inspiring quotes.

In reality, this would be implemented with some kind of user interface (for example, in a browser), but we'll again emulate user interactions using the command line; let's create a new file called quotes_votes.js.

First, in order to vote for a quote, we'll need to reference it, which can be done by the unique _id property. So in quotes_votes.js let's write:

var mongo = require('mongodb'), 
var server = new mongo.Server("localhost", 27017); 
var client = new mongo.Db('quotes', server); 
var params = {id: process.argv[2], voter: process.argv[3]}; 
client.open(function (err, client) { 
  if (err) { throw err; } 
  var collection = new mongo.Collection(client, 'quotes'),  

//vote handling to go here

 collection.find().each(function (err, doc) { 
      if (err) { throw err; } 
      if (doc) { console.log(doc._id, doc.quote); return; } 
      client.close(); 
    }); 
});

Now when we run quotes_votes.js with node, we'll see a list of IDs and quotes. To vote for a quote, we'll simply copy an ID and use it as our command-line parameter. So let's do our vote handling as shown in the following code:

  if (params.id) { 
    collection.update({_id : new mongo.ObjectID(params.id)}, 
      {$inc: {votes: 1}}, {safe: true}
      function (err) { 
        if (err) { throw err; } 

        collection.find().sort({votes: -1}).limit(10).each(function (err, doc) { 
          if (err) { throw err; } 
          if (doc) { 
            var votes = (doc.votes) || 0; 
            console.log(doc.author, doc.quote, votes); 
            return; 
          } 
          client.close(); 
        }); 
      }); 
    return; 
  }

MongoDB IDs must be encoded as a BSON (Binary JSON) ObjectID. Otherwise, the update command will look for param.id as a string, failing to find it. So we create a new mongo.ObjectID(param.id) to convert param.id from a JavaScript string to a BSON ObjectID.

$inc is a MongoDB modifier that performs the incrementing action inside the MongoDB server, essentially allowing us to outsource the calculation. To use it, we pass a document (object) alongside it containing the key to increment and the amount to increase it by. So we pass votes and 1.

$inc will create the votes field if it doesn't exist, and increment it by one (we can also decrement using minus figures). Next are the options to be passed to MongoDB. We've set safe to true, which tells MongoDB to check that the command was successful and send any errors if it wasn't. For the callback to work correctly, safe:true must be passed, otherwise errors are not caught, and the callback occurs immediately.

Tip

Upserting

Another useful option we can set is upsert:true. This is a really convenient MongoDB feature that either updates a record or inserts it if it doesn't exist.

Inside the update callback we run a chain of find.sort.limit.each. find, without any parameters, which returns all our records. sort requires keys and a positive or negative 1, indicating ascending or descending. limit takes an integer of maximum records, and each loops through our records. Inside the each callback, we output every author, quote, and votes of doc, closing the connection when no docs are left.

See also

  • Connecting and sending SQL to a MySQL server discussed in this chapter
  • Storing and retrieving data with Mongoskin discussed in this chapter
  • Storing data to CouchDB with Cradle discussed in this chapter
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.29.224