Node.js and MongoDB - BACK-END PROTOTYPING - Rapid Prototyping with JS: Agile JavaScript Development (2014)

Rapid Prototyping with JS: Agile JavaScript Development (2014)

III. BACK-END PROTOTYPING

6. Node.js and MongoDB

Summary: exhibition of the “Hello World” application in Node.js, list of some of its the most important core modules, NPM workflow, detailed commands for deployment of Node.js apps to Heroku and Windows Azure; MongoDB and its shell, run-time and database Chat applications; example of a test-driven development practice.

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”Martin Fowler

6.1 Node.js

6.1.1 Building “Hello World” in Node.js

To check if you have Node.js installed on your computer, type and execute this command in your terminal:

1 $ node -v

As of this writing, the latest version is 0.8.1. If you don’t have Node.js installed, or if your version is behind, you can download the latest version at nodejs.org/#download.

As usual, you could copy example code from rpjs/hello or write your own program from scratch. If you wish to do the latter, create a folder hello for your “Hello World” Node.js application. Then create file a server.js and line by line type the code below.

This will load the core http module for the server (more on the modules later):

1 var http = require('http');

We’ll need a port number for our Node.js server. To get it from the environment, or assign 1337 if the environment is not set, use:

1 var port = process.env.PORT || 1337;

This will create a server and a call-back function will contain the response handler code:

1 var server = http.createServer(function (req, res) {

To set the right header and status code, use:

1 res.writeHead(200, {'Content-Type': 'text/plain'});

To output “Hello World” with the line end symbol, use:

1 res.end('Hello World\n');

2 });

To set a port and display the address of the server and the port number, use:

1 server.listen(port, function() {

2 console.log('Server is running at %s:%s ',

3 server.address().address, server.address().port);

4 });

From the folder in which you have server.js, launch in your terminal the following command:

1 $ node server.js

Open localhost:1337 or 127.0.0.1:1337 or any other address you see in the terminal as a result of console.log() function, and you should see “Hello World” in a browser. To shut down the server, press Control + C.

information

Note

The name of the main file could be different from server.js, e.g., index.js or app.js. In case you need to launch the app.js file, just use $ node app.js.

6.1.2 Node.js Core Modules

Unlike other programming technologies, Node.js doesn’t come with a heavy standard library. The core modules of node.js are a bare minimum and the rest can be cherry-picked via the Node Package Manager (NPM) registry. The main core modules, classes, methods and events include:

· http

· util

· querystring

· url

· fs

http

This is the main module responsible for Node.js HTTP server. Here are the main methods:

· http.createServer(): returns a new web server object

· http.listen(): begins accepting connections on the specified port and hostname

· http.createClient(): node app can be a client and make requests to other servers

· http.ServerRequest(): incoming requests are passed to request handlers

o data: emitted when a piece of the message body is received

o end: emitted exactly once for each request

o request.method(): the request method as a string

o request.url(): request URL string

· http.ServerResponse(): this object is created internally by an HTTP server — not by the user, and used as an output of request handlers

o response.writeHead(): sends a response header to the request

o response.write(): sends a response body * response.end(): sends and ends a response body

util

This module provides utilities for debugging. Some of the methods include:

· util.inspect(): Return a string representation of an object, which is useful for debugging

querystring

This module provides utilities for dealing with query strings. Some of the methods include:

· querystring.stringify(): Serialize an object to a query string

· querystring.parse(): Deserialize a query string to an object

url

This module has utilities for URL resolution and parsing. Some of the methods include:

· parse(): Take a URL string, and return an object

fs

fs handles file system operations such as reading and writing to/from files. There are synchronous and asynchronous methods in the library. Some of the methods include:

· fs.readFile(): reads file asynchronously

· fs.writeFile(): writes data to file asynchronously

There is no need to install or download core modules. To include them in your application, all you need is to follow the syntax:

1 var http = require('http');

The lists of non-core modules can be found at:

· npmjs.org: Node Package Manager registry

· GitHub hosted list: list of Node.js modules maintainded by Joyent

· nodetoolbox.com: registry based on stats

· Nipster: NPM search tool for Node.js

· Node Tracking: registry based on GitHub stats

If you would like to know how to code your own modules, take a look at the article Your first Node.js module.

6.1.3 Node Package Manager

Node Package Manager, or NPM, manages dependencies and installs modules for you. Node.js installation comes with NPM, whose website is npmjs.org.

package.json contains meta information about our Node.js application such as a version number, author name and, most importantly, what dependencies we use in the application. All of that information is in the JSON formatted object, which is read by NPM.

If you would like to install packages and dependencies specified in package.json, type:

1 $ npm install

A typical package.json file might look like this:

1 {

2 "name": "Blerg",

3 "description": "Blerg blerg blerg.",

4 "version": "0.0.1",

5 "author": {

6 "name" : "John Doe",

7 "email" : "john.doe@gmail.com"

8 },

9 "repository": {

10 "type": "git",

11 "url": "http://github.com/johndoe/blerg.git"

12 },

13 "engines": [

14 "node >= 0.6.2"

15 ],

16 "license" : "MIT",

17 "dependencies": {

18 "express": ">= 2.5.6",

19 "mustache": "0.4.0",

20 "commander": "0.5.2"

21 },

22 "bin" : {

23 "blerg" : "./cli.js"

24 }

25 }

To update a package to its current latest version, or the latest version that is allowable by the version specification defined in package.json, use:

1 $ npm update name-of-the-package

Or for single module installation:

1 $ npm install name-of-the-package

The only module used in the examples — and which does not belong to the core Node.js package — is mongodb. We’ll install it later in the book.

Heroku will need package.json to run NPM on the server.

For more information on NPM, take a look at the article Tour of NPM.

6.1.4 Deploying “Hello World” to PaaS

For Heroku and Windows Azure deployment, we’ll need a Git repository. To create it from the root of your project, type the following command in your terminal:

1 $ git init

Git will create a hidden .git folder. Now we can add files and make the first commit:

1 $ git add .

2 $ git commit -am "first commit"

tip

Tip

To view hidden files on the Mac OS X Finder app, execute this command in a terminal window: defaults write com.apple.finder AppleShowAllFiles -bool true. To change the flag back to hidden: defaults write com.apple.finder AppleShowAllFiles -bool false.

6.1.5 Deploying to Windows Azure

In order to deploy our “Hello World” application to Windows Azure, we must add Git remote. You could copy the URL from Windows Azure Portal, under Web Site, and use it with this command:

1 $ git remote add azure yourURL

Now we should be able to make a push with this command:

1 $ git push azure master

If everything went okay, you should see success logs in the terminal and “Hello World” in the browser of your Windows Azure Web Site URL.

To push changes, just execute:

1 $ git add .

2 $ git commit -m "changing to hello azure"

3 $ git push azure master

A more meticulous guide can be found in the tutorial Build and deploy a Node.js web site to Windows Azure.

6.1.6 Deploying to Heroku

For Heroku deployment, we need to create 2 extra files: Procfile and package.json. You could get the source code from rpjs/hello or write your own one.

The structure of the “Hello World” application looks like this:

1 /hello

2 -package.json

3 -Procfile

4 -server.js

Procfile is a mechanism for declaring what commands are run by your application’s dynos on the Heroku platform. Basically, it tells Heroku what processes to run. Procfile has only one line in this case:

1 web: node server.js

For this example, we keep package.json simple:

1 {

2 "name": "node-example",

3 "version": "0.0.1",

4 "dependencies": {

5 },

6 "engines": {

7 "node": ">=0.6.x"

8 }

9 }

After we have all of the files in the project folder, we can use Git to deploy the application. The commands are pretty much the same as with Windows Azure except that we need to add Git remote, and create Cedar stack with:

1 $ heroku create

After it’s done, we push and update with:

1 $ git push heroku master

2 $ git add .

3 $ git commit -am "changes"

4 $ git push heroku master

If everything went okay, you should see success logs in the terminal and “Hello World” in the browser of your Heroku app URL.

6.2 Chat: Run-Time Memory Version

The first version of the Chat back-end application will store messages only in run-time memory storage for the sake of KISS. That means that each time we start/reset the server, the data will be lost.

We’ll start with a simple test case first to illustrate the Test-Driven Development approach. The full code is available under the rpjs/test folder.

6.3 Test Case for Chat

We should have two methods:

1. Get all of the messages as an array of JSON objects for the GET /message endpoint using the getMessages() method

2. Add a new message with properties name and message for POST /messages route via the addMessage() function

We’ll start by creating an empty mb-server.js file. After it’s there, let’s switch to tests and create the test.js file with the following content:

1 var http = require('http');

2 var assert = require('assert');

3 var querystring = require('querystring');

4 var util = require('util');

5

6 var messageBoard = require('./mb-server');

7

8 assert.deepEqual('[{"name":"John","message":"hi"}]',

9 messageBoard.getMessages());

10 assert.deepEqual ('{"name":"Jake","message":"gogo"}',

11 messageBoard.addMessage ("name=Jake&message=gogo"));

12 assert.deepEqual('[{"name":"John","message":"hi"},{"name":"Jake","message":"gogo"}]',

13 messageBoard.getMessages("name=Jake&message=gogo"));

Please keep in mind that, this is a very simplified comparison of strings and not JavaScript objects. So every space, quote and case matters. You could make the comparison “smarter” by parsing a string into a JSON object with:

1 JSON.parse(str);

For testing our assumptions, we use core the Node.js module assert. It provides a bunch of useful methods like equal(), deepEqual(), etc.

More advanced libraries include alternative interfaces with TDD and/or BDD approaches:

· Should

· Expect

For more Test-Driven Development and cutting-edge automated testing, you could use the following libraries and modules:

· Mocha

· NodeUnit

· Jasmine

· Vows

· Chai

You could copy the “Hello World” script into the mb-server.js file for now or even keep it empty. If we run test.js by the terminal command:

1 $ node test.js

We should see an error. Probably something like this one:

1 TypeError: Object #<Object> has no method 'getMessages'

That’s totally fine, because we haven’t written getMessages() method yet. So let’s do it and make our application more useful by adding two new methods: to get the list of the messages for Chat and to add a new message to the collection.

mb-server.js file with global exports object:

1 exports.getMessages = function() {

2 return JSON.stringify(messages);

3 };

4 exports.addMessage = function (data){

5 messages.push(querystring.parse(data));

6 return JSON.stringify(querystring.parse(data));

7 };

So far, nothing fancy, right? To store the list of messages, we’ll use an array:

1 var messages=[];

2 //this array will hold our messages

3 messages.push({

4 "name":"John",

5 "message":"hi"

6 });

7 //sample message to test list method

tip

Tip

Generally, fixtures like dummy data belong to the test/spec files and not to the main application codebase.

Our server code will look slightly more interesting. For getting the list of messages, according to REST methodology, we need to make a GET request. For creating/adding a new message, it should be a POST request. So in our createServer object, we should add req.method() and req.url()to check for an HTTP request type and a URL path.

Let’s load the http module:

1 var http = require('http');

We’ll need some of the handy functions from the util and querysting modules (to serialize and deserialize objects and query strings):

1 var util = require('util');

2 var querystring = require('querystring');

To create a server and expose it to outside modules (i.e., test.js):

1 exports.server=http.createServer(function (req, res) {

Inside of the request handler callback, we should check if the request method is POST and the URL is messages/create.json:

1 if (req.method == "POST" &&

2 req.url == "/messages/create.json") {

If the condition above is true, we add a message to the array. However, data must be converted to a string type (with encoding UTF-8) prior to the adding, because it is a type of Buffer:

1 var message = '';

2 req.on('data', function(data, message){

3 console.log(data.toString('utf-8'));

4 message = exports.addMessage(data.toString('utf-8'));

These logs will help us to monitor the server activity in the terminal:

1 })

2 console.log(util.inspect(message, true, null));

3 console.log(util.inspect(messages, true, null));

The output should be in a text format with a status of 200 (okay):

1 res.writeHead(200, {

2 'Content-Type': 'text/plain'

3 });

We output a message with a newly created object ID:

1 res.end(message);

2 }

If the method is GET and the URL is /messages/list.json output a list of messages:

1 if (req.method == "GET" &&

2 req.url == "/messages/list.json") {

Fetch a list of messages:

1 var body = exports.getMessages();

The response body will hold our output:

1 res.writeHead(200, {

2 'Content-Length': body.length,

3 'Content-Type': 'text/plain'

4 });

5 res.end(body);

6 }

7 else {

This sets the right header and status code:

1 res.writeHead(200, {

2 'Content-Type': 'text/plain'

3 });

In case it’s neither of the two endpoints above, we output a string with a line-end symbol:

1 res.end('Hello World\n');

2 };

3 console.log(req.method);

1 }).listen(1337, "127.0.0.1");

Now, we should set a port and IP address of the server:

1 console.log('Server running at http://127.0.0.1:1337/');

We expose methods for the unit testing in test.js (exports keyword), and this function returns an array of messages as a string/text:

1 exports.getMessages = function() {

2 return JSON.stringify(messages);

3 };

addMessage() converts a string into a JavaScript object with the parse/deserializer method from querystring:

1 exports.addMessage = function (data){

2 messages.push(querystring.parse(data));

Also returning a new message in a JSON-as-a-string format:

1 return JSON.stringify(querystring.parse(data));

2 };

Here is the full code of mb-server.js. It’s also available at rpjs/test:

1 var http = require('http');

2 //loads http module

3 var util = require('util');

4 //useful functions

5 var querystring = require('querystring');

6 //loads querystring module,

7 //we'll need it to serialize and

8 //deserialize objects and query strings

9

10 var messages=[];

11 //this array will hold our messages

12 messages.push({

13 "name": "John",

14 "message": "hi"

15 });

16 //sample message to test list method

17

18 exports.server=http.createServer(function (req, res) {

19 //creates server

20 if (req.method == "POST" &&

21 req.url == "/messages/create.json") {

22 //if method is POST and

23 //URL is messages/ add message to the array

24 var message = '';

25 req.on('data', function(data, message){

26 console.log(data.toString('utf-8'));

27 message = exports.addMessage(data.toString('utf-8'));

28 //data is type of Buffer and

29 //must be converted to string

30 //with encoding UTF-8 first

31 //adds message to the array

32 })

33 console.log(util.inspect(message, true, null));

34 console.log(util.inspect(messages, true, null));

35 //debugging output into the terminal

36 res.writeHead(200, {

37 'Content-Type': 'text/plain'

38 });

39 //sets the right header and status code

40 res.end(message);

41 //out put message, should add object id

42 }

43 if (req.method == "GET" &&

44 req.url == "/messages/list.json") {

45 //if method is GET and

46 //URL is /messages output list of messages

47 var body = exports.getMessages();

48 //body will hold our output

49 res.writeHead(200, {

50 'Content-Length': body.length,

51 'Content-Type': 'text/plain'

52 });

53 res.end(body);

54 }

55 else {

56 res.writeHead(200, {

57 'Content-Type': 'text/plain'

58 });

59 //sets the right header and status code

60 res.end('Hello World\n');

61 };

62 console.log(req.method);

63 //outputs string with line end symbol

64 }).listen(1337, "127.0.0.1");

65 //sets port and IP address of the server

66 console.log('Server running at http://127.0.0.1:1337/');

67

68 exports.getMessages = function() {

69 return JSON.stringify(messages);

70 //output array of messages as a string/text

71 };

72 exports.addMessage = function (data){

73 messages.push(querystring.parse(data));

74 //to convert string into

75 //JavaScript object we use parse/deserializer

76 return JSON.stringify(querystring.parse(data));

77 //output new message in JSON as a string

78 };

To check it, go to localhost:1337/messages/list.json. You should see an example message. Alternatively, you could use the terminal command:

1 $ curl http://127.0.0.1:1337/messages/list.json

To make the POST request by using a command line interface:

1 curl -d "name=BOB&message=test" http://127.0.0.1:1337/messages/create.json

And you should get the output in a server terminal window and a new message “test” when you refresh localhost:1337/messages/list.json. Needless to say, all three tests should pass.

Your application might grow bigger with more methods, URL paths to parse and conditions. That is where frameworks come in handy. They provide helpers to process requests and other nice things like static file support, sessions, etc. In this example, we intentionally didn’t use any frameworks like Express (http://expressjs.com/) or Restify (http://mcavage.github.com/node-restify/). Other notable Node.js frameworks:

· Derby: MVC framework making it easy to write real-time, collaborative applications that run in both Node.js and browsers

· Express.js: the most robust, tested and used Node.js framework

· Restify: lightweight framework for REST API servers

· Sails.js: MVC Node.js framework

· hapi: Node.js framework built on top of Express.js

· Connect: a middleware framework for node, shipping with over 18 bundled middlewares and a rich selection of third-party middleware

· GeddyJS: a simple, structured MVC web framework for Node

· CompoundJS (ex-RailswayJS): Node.JS MVC framework based on ExpressJS

· Tower.js: a full stack web framework for Node.js and the browser

· Meteor: open-source platform for building top-quality web apps in a fraction of the time

Ways to improve the application:

· Improve existing test cases by adding object comparison instead of a string one

· Move the seed data to test.js from mb-server.js

· Add test cases to support your front-end, e.g., up-vote, user log in

· Add methods to support your front-end, e.g., up-vote, user log in

· Generate unique IDs for each message and store them in a Hash instead of an Array

· Install Mocha and re-factor test.js so it uses this library

So far we’ve been storing our messages in the application memory, so each time the application is restarted, we lose it. To fix it, we need to add a persistence, and one of the ways is to use a database like MongoDB.

6.4 MongoDB

6.4.1 MongoDB Shell

If you haven’t done so already, please install the latest version of MongoDB from mongodb.org/downloads. For more instructions, please refer to the Setup, Database: MongoDB section.

Now from the folder where you unpacked the archive, launch the mongod service with:

1 $ ./bin/mongod

You should be able to see information in your terminal and in the browser at localhost:28017.

For the MongoDB shell, or mongo, launch in a new terminal window (important!), and at the same folder this command:

1 $ ./bin/mongo

You should see something like this, depending on your version of the MongoDB shell:

1 MongoDB shell version: 2.0.6

2 connecting to: test

To test the database, use the JavaScript-like interface and commands save and find:

1 > db.test.save( { a: 1 } )

2 > db.test.find()

More detailed step-by-step instructions are available at Setup, Database: MongoDB.

Some other useful MongoDB shell commands:

1 > help

2 > show dbs

3 > use board

4 > show collections

5 > db.messages.remove();

6 > var a = db.messages.findOne();

7 > printjson(a);

8 > a.message = "hi";

9 > db.messages.save(a);

10 > db.messages.find({});

11 > db.messages.update({name: "John"},{$set: {message: "bye"}});

12 > db.messages.find({name: "John"});

13 > db.messages.remove({name: "John"});

A full overview of the MongoDB interactive shell is available at mongodb.org: Overview - The MongoDB Interactive Shell.

6.4.2 MongoDB Native Driver

We’ll use Node.js Native Driver for MongoDB (https://github.com/christkv/node-mongodb-native) to access MongoDB from Node.js applications. Full documentation is also available at http://mongodb.github.com/node-mongodb-native/api-generated/db.html.

To install MongoDB Native driver for Node.js, use:

1 $ npm install mongodb

More details are at http://www.mongodb.org/display/DOCS/node.JS.

Don’t forget to include the dependency in the package.json file as well:

1 {

2 "name": "node-example",

3 "version": "0.0.1",

4 "dependencies": {

5 "mongodb":"",

6 ...

7 },

8 "engines": {

9 "node": ">=0.6.x"

10 }

11 }

Alternatively, for your own development you could use other mappers, which are available as an extension of the Native Driver:

· Mongoskin: the future layer for node-mongodb-native

· Mongoose: asynchronous JavaScript driver with optional support for modeling

· Mongolia: lightweight MongoDB ORM/driver wrapper

· Monk: Monk is a tiny layer that provides simple yet substantial usability improvements for MongoDB usage within Node.js

This small example will test if we can connect to local MongoDB instance from a Node.js script.

After we installed the library, we can include the mongodb library in our db.js file:

1 var util = require('util');

2 var mongodb = require ('mongodb');

This is one of the ways to establish connection to the MongoDB server in which the db variable will hold reference to the database at a specified host and port:

1 var Db = mongodb.Db;

2 var Connection = mongodb.Connection;

3 var Server = mongodb.Server;

4 var host = '127.0.0.1';

5 var port = 27017;

6

7 var db=new Db ('test', new Server(host,port, {}));

To actually open a connection:

1 db.open(function(e,c){

2 //do something with the database here

3 // console.log (util.inspect(db));

4 console.log(db._state);

5 db.close();

6 });

This code snippet is available under the rpjs/db/db.js folder. If we run it, it should output “connected” in the terminal. When you’re in doubt and need to check the properties of an object, there is a useful method in the util module:

1 console.log(util.inspect(db));

6.4.3 MongoDB on Heroku: MongoHQ

After you made your application that displays ‘connected’ work locally, it’s time to slightly modify it and deploy to the platform as a service, i.e., Heroku.

We recommend using the MongoHQ add-on, which is a part of MongoHQ technology. It provides a browser-based GUI to look up and manipulate the data and collections. More information is available at https://devcenter.heroku.com/articles/mongohq.

information

Note

You might have to provide your credit card information to use MongoHQ even if you select the free version. You should not be charged, though.

In order to connect to the database server, there is a database connection URL (a.k.a. MongoHQ URL/URI), which is a way to transfer all of the necessary information to make a connection to the database in one string.

The database connection string MONGOHQ_URL has the following format:

1 mongodb://user:pass@server.mongohq.com/db_name

You could either copy the MongoHQ URL string from the Heroku website (and hard-code it) or get the string from the Node.js process.env object:

1 process.env.MONGOHQ_URL;

or

1 var connectionUri = url.parse(process.env.MONGOHQ_URL);

tip

Tip

The global object process gives access to environment variables via process.env. Those variables conventionally used to pass database host names and ports, passwords, API keys, port numbers, and other system information that shouldn’t be hard-coded into the main logic.

To make our code work both locally and on Heroku, we can use the logical OR operator || and assign a local host and port if environment variables are undefined:

1 var port = process.env.PORT || 1337;

2 var dbConnUrl = process.env.MONGOHQ_URL ||

3 'mongodb://@127.0.0.1:27017';

Here is out updated cross-environment ready db.js file:

1 var url = require('url')

2 var util = require('util');

3 var mongodb = require ('mongodb');

4 var Db = mongodb.Db;

5 var Connection = mongodb.Connection;

6 var Server = mongodb.Server;

7

8 var dbConnUrl = process.env.MONGOHQ_URL ||

9 'mongodb://127.0.0.1:27017';

10 var host = url.parse(dbConnUrl).hostname;

11 var port = new Number(url.parse(dbConnUrl).port);

12

13 var db=new Db ('test', new Server(host,port, {}));

14 db.open(function(e,c){

15 // console.log (util.inspect(db));

16 console.log(db._state);

17 db.close();

18 });

Following the modification of db.js by addition of MONGOHQ_URL, we can now initialize Git repository, create a Heroku app, add the MongoHQ add-on to it and deploy the app with Git.

Utilize the same steps as in the previous examples to create a new git repository:

1 git init

2 git add .

3 git commit -am 'initial commit'

Create the Cedar stack Heroku app:

1 $ heroku create

If everything went well you should be able to see a message that tell you the new Heroku app name (and URL) along with a message that remote was added. Having remote in your local git is crucial, you can always check a list of remote by:

1 git remote show

To install free MongoHQ on the existing Heroku app (add-ons work on a per app basis), use:

1 $ heroku addons:add mongohq:sandbox

Or log on to addons.heroku.com/mongohq with your Heroku credentials and choose MongoHQ Free for that particular Heroku app, if you know the name of that app.

If you get db.js and modified db.js files working, let’s enhance by adding a HTTP server, so the ‘connected’ message will be displayed in the browser instead of the terminal window. To do so, we’ll wrap the server object instanciation in a database connection callback:

1 ...

2 db.open(function(e,c){

3 // console.log (util.inspect(db));

4 var server = http.createServer(function (req, res) {

5 //creates server

6 res.writeHead(200, {'Content-Type': 'text/plain'});

7 //sets the right header and status code

8 res.end(db._state);

9 //outputs string with line end symbol

10 });

11 server.listen(port, function() {

12 console.log('Server is running at %s:%s '

13 , server.address().address

14 , server.address().port);

15 //sets port and IP address of the server

16 });

17 db.close();

18 });

19 ...

The final deployment-ready file app.js from rpjs/db:

1 /*

2 Rapid Prototyping with JS is a JavaScript

3 and Node.js book that will teach you how to build mobile

4 and web apps fast. — Read more at

5 http://rapidprototypingwithjs.com.

6 */

7 var util = require('util');

8 var url = require('url');

9 var http = require('http');

10 var mongodb = require('mongodb');

11 var Db = mongodb.Db;

12 var Connection = mongodb.Connection;

13 var Server = mongodb.Server;

14 var port = process.env.PORT || 1337;

15 var dbConnUrl = process.env.MONGOHQ_URL ||

16 'mongodb://@127.0.0.1:27017';

17 var dbHost = url.parse(dbConnUrl).hostname;

18 var dbPort = new Number(url.parse(dbConnUrl).port);

19 console.log(dbHost + dbPort)

20 var db = new Db('test', new Server(dbHost, dbPort, {}));

21 db.open(function(e, c) {

22 // console.log (util.inspect(db));

23 // creates server

24 var server = http.createServer(function(req, res) {

25 //sets the right header and status code

26 res.writeHead(200, {

27 'Content-Type': 'text/plain'

28 });

29 //outputs string with line end symbol

30 res.end(db._state);

31 });

32 //sets port and IP address of the server

33 server.listen(port, function() {

34 console.log(

35 'Server is running at %s:%s ',

36 server.address().address,

37 server.address().port);

38 });

39 db.close();

40 });

After the deployment you should be able to open the URL provided by Heroku and see the message ‘connected’.

Here is the manual on how to use MongoDB from Node.js code: mongodb.github.com/node-mongodb-native/api-articles/nodekoarticle1.html.

Another approach is to use the MongoHQ Module, which is available at github.com/MongoHQ/mongohq-nodejs.

This example illustrates a different use of the mongodb library by outputting collections and a document count. The full source code from rpjs/db/collections.js:

1 var mongodb = require('mongodb');

2 var url = require('url');

3 var log = console.log;

4 var dbUri = process.env.MONGOHQ_URL || 'mongodb://localhost:27017/test';

5

6 var connectionUri = url.parse(dbUri);

7 var dbName = connectionUri.pathname.replace(/^\//, '');

8

9 mongodb.Db.connect(dbUri, function(error, client) {

10 if (error) throw error;

11

12 client.collectionNames(function(error, names){

13 if(error) throw error;

14

15 //output all collection names

16 log("Collections");

17 log("===========");

18 var lastCollection = null;

19 names.forEach(function(colData){

20 var colName = colData.name.replace(dbName + ".", '')

21 log(colName);

22 lastCollection = colName;

23 });

24 if (!lastCollection) return;

25 var collection = new mongodb.Collection(client, lastCollection);

26 log("\nDocuments in " + lastCollection);

27 var documents = collection.find({}, {limit:5});

28

29 //output a count of all documents found

30 documents.count(function(error, count){

31 log(" " + count + " documents(s) found");

32 log("====================");

33

34 // output the first 5 documents

35 documents.toArray(function(error, docs) {

36 if(error) throw error;

37

38 docs.forEach(function(doc){

39 log(doc);

40 });

41

42 // close the connection

43 client.close();

44 });

45 });

46 });

47 });

We’ve used a shortcut for console.log() with var log = console.log; and return as a control flow via if (!lastCollection) return;.

6.4.4 BSON

Binary JSON, or BSON, it is a special data type which MongoDB utilizes. It is like to JSON in notation, but has support for additional more sophisticated data types.

warning

Warning

A word of caution about BSON: ObjectId in MongoDB is an equivalent to ObjectID in MongoDB Native Node.js Driver, i.e., make sure to use the proper case. Otherwise you’ll get an error. More on the types: ObjectId in MongoDB vs Data Types in MongoDB Native Node.js Drier. Example of Node.js code with mongodb.ObjectID(): collection.findOne({_id: new ObjectID(idString)}, console.log) // ok. On the other hand, in the MongoDB shell, we employ: db.messages.findOne({_id:ObjectId(idStr)});.

6.5 Chat: MongoDB Version

We should have everything set up for writing the Node.js application which will work both locally and on Heroku. The source code is available under rpjs/mongo. The structure of the application is simple:

1 /mongo

2 -web.js

3 -Procfile

4 -package.json

This is what web.js looks like; first we include our libraries:

1 var http = require('http');

2 var util = require('util');

3 var querystring = require('querystring');

4 var mongo = require('mongodb');

Then put out a magic string to connect to MongoDB:

1 var host = process.env.MONGOHQ_URL || "mongodb://@127.0.0.1:27017/twitter-clone";

2 //MONGOHQ_URL=mongodb://user:pass@server.mongohq.com/db_name

information

Note

The URI/URL format contains the optional database name in which our collection will be stored. Feel free to change it to something else, for example ‘rpjs’ or ‘test’.

We put all the logic inside of an open connection in the form of a callback function:

1 mongo.Db.connect(host, function(error, client) {

2 if (error) throw error;

3 var collection = new mongo.Collection(

4 client,

5 'messages');

6 var app = http.createServer(function(request, response) {

7 if (request.method === "GET" &&

8 request.url === "/messages/list.json") {

9 collection.

10 find().

11 toArray(function(error,results) {

12 response.writeHead(200,{

13 'Content-Type':'text/plain'

14 });

15 console.dir(results);

16 response.end(JSON.stringify(results));

17 });

18 };

19 if (request.method === "POST" &&

20 request.url === "/messages/create.json") {

21 request.on('data', function(data) {

22 collection.insert(

23 querystring.parse(data.toString('utf-8')),

24 {safe:true},

25 function(error, obj) {

26 if (error) throw error;

27 response.end(JSON.stringify(obj));

28 }

29 )

30 })

31 };

32 });

33 var port = process.env.PORT || 5000;

34 app.listen(port);

35 })

information

Note

We don’t have to use additional words after the collection/entity name, i.e., instead of /messages/list.json and /messages/create.json it’s perfectly fine to have just /messages for all the HTTP methods such as GET, POST, PUT, DELETE. If you change them in your application code make sure to use the updated CURL commands and front-end code.

To test via CURL terminal commands run:

1 curl http://localhost:5000/messages/list.json

Or open your browser at the http://locahost:5000/messages/list.json location.

It should give you an empty array: [] which is fine. Then POST a new message:

1 curl -d "username=BOB&message=test" http://localhost:5000/messages/create.json

Now we must see a response containing an ObjectID of a newly created element, for example: [{"username":"BOB","message":"test","_id":"51edcad45862430000000001"}]. Your ObjectId might vary.

If everything works as it should locally, try to deploy it to Heroku.

To test the application on Heroku, you could use the same CURL commands, substituting http://localhost/ or “http://127.0.0.1” with your unique Heroku app’s host/URL:

1 $ curl http://your-app-name.herokuapp.com/messages/list.json

2 $ curl -d "username=BOB&message=test"

3 http://your-app-name.herokuapp.com/messages/create.json

It’s also nice to double check the database either via Mongo shell: $ mongo terminal command and then use twitter-clone and db.messages.find(); or via MongoHub, mongoui, mongo-express or in case of MongoHQ through its web interface accessible at heroku.com website.

If you would like to use another domain name instead of http://your-app-name.herokuapp.com, you’ll need to do two things:

1. Tell Heroku your domain name:

2. 1 $ heroku domains:add www.your-domain-name.com

3. Add the CNAME DNS record in your DNS manager to point to http://your-app-name.herokuapp.com.

More information on custom domains can be found at devcenter.heroku.com/articles/custom-domains.

tip

Tip

For more productive and efficient development we should automate as much as possible, i.e., use tests instead of CURL commands. There is an article on the Mocha library in the BONUS chapter which, along with the superagent or request libraries, is a timesave