CouchDB stores views in special documents called design documents. You use regular REST commands to add and remove design documents, just like you would any other document. So we need to PUT into CouchDB a design document that contains our views.
Let’s write a script that creates or updates a design document to house these views. Open your editor again and enter this:
databases/make-views.js
#!/usr/bin/env node --harmony
'use strict'; const
async = require('async'), request = require('request'), views = require('./lib/views.js'); async.waterfall([
// get the existing design doc (if present)
function(next) {
request.get('http://localhost:5984/books/_design/books', next); },
// create a new design doc or use existing
function(res, body, next) { if (res.statusCode === 200) { next(null, JSON.parse(body)); } else if (res.statusCode === 404) { next(null, { views: {} }); } },
// add views to document and submit
function(doc, next) { Object.keys(views).forEach(function(name) { doc.views[name] = views[name]; }); request({ method: 'PUT', url: 'http://localhost:5984/books/_design/books', json: doc }, next); }
], function(err, res, body) { if (err) { throw err; }
console.log(res.statusCode, body); });
Save the file as make-views.js in your project directory and make the file exe- cutable using chmod in a terminal. This program uses the async module’s
waterfall() method to execute a sequence of asynchronous functions. Using the waterfall method cleans up code that would otherwise have to be highly indented due to nested callbacks.
waterfall() takes two arguments: an array of functions to execute sequentially and a summary function to call when everything is finished. Each function in the array takes some number of arguments and finally a next callback to pass results forward into the next function.
Our first function kicks off a request for the design document called
_design/books, using the next function as its callback. If this is your first time running this program, there won’t be a _desing/books document yet and the request will return a 404 Not Found status code.
The second function checks to see whether we got a 404 Not Found or a 200 OK. For a 200 OK, we just want to parse out the JSON returned in the response body and pass that forward. For a 404 Not Found, we have to create a skeleton design document to pass to the next function.
The third function copies the views we put in the views.js module onto the design document. Then it issues a PUT request to the database to save the newly created or updated document. Like our earlier request, we use the next
function for the callback to request().
Finally, the summary function reports the returned status code and response body from the last request. This will produce output just like our dbcli.js
program.
Let’s try it out! Open a terminal and run make-views.js:
$ ./make-views.js 201 { ok: true,
id: '_design/books',
rev: '1-7e273ea81e766d078ea17f0c4950fe2a' }
OK, cool—the design doc is in the database. Now we should be able to query the views. Like all gestures in CouchDB, querying a view is done via REST. For example, we can get a list of all authors and the number of books to their name. We do this by getting the _design/books document with the special suffix
_view/by_author to tell CouchDB that we want the results of the generated view:
$ ./dbcli.js GET books/_design/books/_view/by_author?group=true 200 { rows:
[ { key: '"Colored Quartet" (name unknown)', value: 1 }, { key: '(Mrs.) L. P.', value: 1 },
{ key: '`Abdu\'l-Bahá', value: 19 },
{ key: 'A Gentleman of Elvas [pseud.]', value: 1 }, { key: 'A--a', value: 1 },
{ key: 'A-No. 1', value: 1 }, { key: 'A. C. F.', value: 1 }, { key: 'A. L. O. E.', value: 5 }, { key: 'A.E.', value: 2 }, ...
It may take a while for the results to come back, especially if you just recently added the _design/books document. To process the map function,
CouchDB sends every document in the database through it to produce the view. As new documents are added, CouchDB will incrementally update the generated views (it doesn’t have to run through them all each time).
The group=true parameter tells CouchDB that we want the result of running the reduce function as well. Since our reduce function is a basic count, the
value field of each row is the number of books attributed to that author. So, for example, the author “A.E.” is credited with two books.
CouchDB allows you to ask for a subset of view records by specifying a spe- cific key or a startkey and endkey as URL parameters. We’ll use these capabilities in Chapter 6, Scalable Web Services, on page 87, when we build our own RESTful interface for querying the data.
Wrapping Up
This chapter covered a lot of ground against the backdrop of working with databases. To start, we created a project directory complete with a package.json
file for managing dependencies through npm.
By choosing CouchDB—a RESTful, JSON-based, document-oriented data- store—we got a detailed look at how RESTful APIs work. We used the request
module to simplify making HTTP client requests from Node.
Step by step, you created scripts to import large amounts of data into CouchDB with Node. We saw how Node’s extreme speed sometimes works against it, exhausting system resources and overwhelming other services. To fix this, we explored techniques for queuing work using the async module. Along the way you learned how to use nodeunit to develop and run unit tests for your modules.
Robustness
We dealt with the ECONNRESET problem in Putting It All Together, on page 78, by turning the concurrency down from 1,000 to 10.
• What would happen if we still got an ECONNRESET? • How would you change the code to retry the operation?