The purpose of this assignment is to learn how to read documents from a CouchDB database from inside a Node.js program. This will also give you experience accessing remote services using HTTP. Such services are sometimes referred to as Web services and are very common in software systems these days.
This assignment builds on the ajax and couch assignments. In the ajax assignment, we created a Web page that uses ajax to request a message string from the server, which it displays to the user. However, the message string was hard-coded into the program. In this assignment, we will use what we learned in the couch assignment to retrieve the message string from a CouchDB database.
Create a directory named read for the work you do for this assignment. At the end of the assignment, this folder will contain the following files.
Copy your app server code from the cache assignment to the read folder as a starting point.
Start your CouchDB server if not already started.
Test that the application is working correctly by visiting http://localhost:5000/ in a browser.
In this step we create a partially implemented
module named db
that provides a layer of abstraction
between the request handlers and the database.
The db
module exports
a single function named getMessage
.
We also modify message
,
the module that handles requests for the message.
Actual interaction with the database will be done in a later step.
Create db.js with the following contents.
exports.getMessage = function(cb) { cb(null, Math.random() + ''); };
The getMessage
function is implemented as a stub
for this intermediate step of development.
The purpose of this function is to retrieve a message string
from the database.
If for whatever reason there is a problem getting this string,
the callback function cb
is called with
an instance of Error
as its sole argument.
If there is no problem, then cb
is called
with null
as its first argument and the message string
as its second argument.
We use the random number generator to simulate changing message data.
The random
function returns a number,
which we convert to a string by concatinating with the empty string.
There is one issue with the stub code for
the getMessage function: it invokes the callback immediately.
When interacting with the database, we will need to invoke
asynchronous functions,
which means we will return from getMessage
before the callback
cb
is called.
But our stub code returns from getMessage
after
the callback cb
is called.
As a result, the stub code might permit logic errors
that could be caught at this point.
The following code is an improved version of the stub code.
exports.getMessage = function(cb) { setTimeout(function() { cb(null, Math.random() + ''); }, 4); };
The above version of getMessage
has the function
returning before the callback is invoked,
which is the normal sequence of events with asynchronous functions.
Another improvement we can make is to alternate between returning an error and returning a message. This will test our error hanlding code. The following version of the stub code accomplishes this.
var error = false; exports.getMessage = function(cb) { setTimeout(function() { if (error) cb(new Error('database server error')); else cb(null, Math.random() + ''); error = !error; }, 4); };
With our database code stubbed out,
we can now make changes to the message request handing code,
which is in the module message
.
The following code is a rewrite of this
module so that the getMessage
function
of the db
module is used to get the message to return to the client.
var http = require('http'); var db = require('./db'); exports.handle = function(req, res) { db.getMessage(function(err, message) { if (err) { console.log(err.message); res.writeHead(500, 'server error'); res.end(); return; } var responseObject = { msg: message }; var responseString = JSON.stringify(responseObject); var responseBody = new Buffer(responseString, 'utf-8'); res.writeHead(200, { 'Content-Type': 'application/json', 'Content-Length': responseBody.length, 'Pragma': 'no-cache', 'Cache-Control': 'no-cache, no-store' }); res.end(responseBody); }); };
Note that we require
the db
module
using an absolute pathname './db'
.
We do this because Nodejs does not search the current directory
when resolving references to modules.
Also note that we need to create a Buffer
object
for each request. This is required because
the message data may change between requests.
Test your implementation by running the server and testing with a browser.
Use what you learned from the CouchDB assignment
to modify the file createdb.sh
to create a database called read.
Insert a single document into this database
with a property named text.
This property should be set to the string "the read assignment".
Also, specify the value message for the _id
field so that we can identify
this document in a straightforward way.
Modify test_update.sh so that it changes the message in the database to "the new message".
Replace the stub code in db.js
with the following code.
var assert = require('assert'); var querystring = require('querystring'); var http = require('http'); exports.getMessage = function(cb) { var options = { hostname: 'localhost', auth: 'admin:1234', port: 5984, path: '/read/message', method: 'GET' }; send(options, function(err, data) { if (err) { console.log(err.message); return cb(err); } if (data.text === undefined) { throw new Error( 'text property missing from msg document.\n' + JSON.stringify(data) ); } cb(null, data.text); }); }; // Send request and receive data (a Javascript object). // The options argument is the same as for http.request. // cb = function(err, data) function send(options, cb) { var req; // create request req = http.request(options, function(res) { var dataString; // to be converted to Javascript object // tell node how to convert received bytes to a Javascript string res.setEncoding('utf8'); // accumulate data res.on('data', function (chunk) { if (dataString === undefined) dataString = chunk; else dataString += chunk; }); // parse received data res.on('end', function() { var data; try { data = JSON.parse(dataString); } catch (err) { return cb(err); } cb(null, data); }); }); // register error listener // pass error directly to callback req.on('error', cb); // send request req.end(); };
The db.getMessage
function calls the send
function,
which calls the Node.js request
function
from the http
module.
The request
function returns an instance of ClientRequest
.
This is used to send an HTTP message to the database server.
So, in this part of our code,
our Web server plays the role of a Web client.
First, test that the browser gets the current message from our server and the server code runs without error.
Second, while our server is running, run test_update.sh to set a different message. Then click on the make a request link to see that the new message is displayed to the user. Do this test without restarting the Web application server.