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.
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.
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.
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:
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.
Let's check out some other useful MongoDB features.
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.
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. .
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.
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.
18.118.29.224