Writing Modular Express Services

Throughout the remainder of the chapter, we’re going to build a RESTful web service with Express for creating and managing book bundles. These are basically named reading lists. Our app will be called Better Book Bundle Builder (or b4 for short).

We’ll work extensively with the books database we set up in Chapter 5, Accessing Databases, as well as a new database called b4. Our application will work roughly as follows:

  • It will communicate with two databases: the books database and the b4 database.

  • To the b4 application, the books database is read-only (we will not add, delete, or overwrite any documents in it).

  • The b4 database will store user data, including the book bundles that users make.

To create the b4 database, make sure CouchDB is running, then use curl from the command line:

 
$ ​curl -X PUT http://localhost:5984/b4
 
{"ok":true}

The -X flag lets you specify which HTTP method to use. We’ll use curl a lot in this chapter for testing our web services.

Now we’re ready to create our modular, RESTful web services.

Separating Server Code into Modules

Just like our Hello World example, the main entry point for the b4 service is the server.js file. But instead of assigning a handler with app.get() directly, now we specify some configuration parameters and pull in the API modules.

Here’s the part of b4/server.js that differs from the Hello World version:

web-services/b4/server.js
 
const​ config = {
 
bookdb: ​'http://localhost:5984/books/'​,
 
b4db: ​'http://localhost:5984/b4/'
 
};
 
 
require(​'./lib/book-search.js'​)(config, app);
 
require(​'./lib/field-search.js'​)(config, app);
 
require(​'./lib/bundle.js'​)(config, app);

Each of the three API modules is a function that takes two arguments: our config hash and the Express app to add routes to.

Let’s run the server and then we’ll dig into the API modules. This time, instead of using npm start, we’ll use nodemon. Short for “Node Monitor,” nodemon runs a Node.js program and then automatically restarts it whenever the source code changes.

To get nodemon, install it globally through npm:

 
$ ​npm install -g nodemon

Now use nodemon to start up the b4 service.

 
$ ​nodemon --harmony server.js
 
16 Sep 19:05:50 - [nodemon] v0.7.8
 
16 Sep 19:05:50 - [nodemon] to restart at any time, enter `rs`
 
16 Sep 19:05:50 - [nodemon] watching: ./web-services/b4
 
16 Sep 19:05:50 - [nodemon] starting `node --harmony server.js`
 
ready captain.

Now let’s dig into the search APIs.

Implementing Search APIs

To build a book bundle, a user has to be able to discover books to add to it. So our modular web service will have two search APIs: field search (for discovering authors and subjects) and book search (for finding books by a given author or subject).

The Node.js code for these two APIs is quite similar—so we’ll focus on the field search here and leave the book search for the wrap-up questions at the end.

The field search API helps users to find authors or subjects based on a starting string. For example, to get a list of subjects that start with Croc, you’d make a request like this:

 
$ ​curl http://localhost:3000/api/search/subject?q=Croc
 
[
 
"Crocheting",
 
"Crocheting -- Patterns",
 
"Crockett, Davy, 1786-1836",
 
"Crocodiles",
 
"Crocodiles -- Juvenile fiction"
 
]

This API could be employed, for instance, in a user interface so that when a user starts typing a subject, suggestions automatically pop up.

Let’s take a look at how this is accomplished in code. Here is the content of the lib/field-search.js file in your b4 project:

web-services/b4/lib/field-search.js
Line 1 
'use strict'​;
const​ request = require(​'request'​);
module.exports = ​function​(config, app) {
app.get(​'/api/search/:view'​, ​function​(req, res) {
request({
method: ​'GET'​,
url: config.bookdb + ​'_design/books/_view/by_'​ + req.params.view,
qs: {
startkey: JSON.stringify(req.query.q),
10 
endkey: JSON.stringify(req.query.q + ​"ufff0"​),
group: true
}
}, ​function​(err, couchRes, body) {
15 
// couldn't connect to CouchDB
if​ (err) {
res.json(502, { error: ​"bad_gateway"​, reason: err.code });
return​;
}
20 
// CouchDB couldn't process our request
if​ (couchRes.statusCode !== 200) {
res.json(couchRes.statusCode, JSON.parse(body));
return​;
25 
}
// send back just the keys we got back from CouchDB
res.json(JSON.parse(body).rows.map(​function​(elem){
return​ elem.key;
30 
}));
});
});
};

This program does a lot in a small space. Let’s dig into it a piece at a time.

First, in line 3, we set module.exports to a function. This is how we create a module that is itself a function (like request and express). When you write modules that do just one thing, this is a handy pattern.

Next up, we call app.get() in line 4 to register a route with the Express app. Once the route callback function starts, it immediately makes a request to CouchDB, starting on line 5. To build out the request URL, we use the value in req.params.view, which Express extracted from the URL.

So, for instance, when someone requests /api/search/subject, this will trigger a request to the CouchDB view _design/books/_view/by_subject. Notice that we don’t check whether req.params.view is one of our expected values—this is left as an exercise at the end of the chapter.

To limit the results coming back from CouchDB, we specify query string parameters in the qs object. We pull the incoming URL parameter q on line 9. When an incoming request contains ?q=Croc, then req.query.q will be the string “Croc” (without quotes).

When receiving a request for a view, CouchDB uses the startkey and endkey parameters to bind the results. In our case, we want all the keys that start with the query string. Using req.query.q + "ufff0" for the endkey guarantees that we’ll get all the keys that start with our query param. And setting group to true for this view tells CouchDB that we want unique keys only (no duplicates).

Finally, starting on line 13, we have our handler function for when the CouchDB request returns. There are three outcomes we have to account for:

  • The error case—CouchDB didn’t respond, so we should send 502 Bad Gateway back to the requester.

  • The non-success case—CouchDB responded, but it wasn’t the HTTP 200 OK result that we expected, so we pass it verbatim back to the requester.

  • The success case—CouchDB responded with the data we’re after, so we parse the JSON and extract just the keys to send back.

This example demonstrates some of the challenges you’ll face writing RESTful APIs. The nice thing about REST is that it gives you a good basis for an API. For a variety of cases, the HTTP specification prescribes very specific status codes.[31]

However, doing it right can be a challenge since you have to account for a variety of success and non-success cases. Promises offer one way of managing asynchronous code that can help with these challenges, as we’ll see in upcoming sections as we build out more RESTful APIs.

..................Content has been hidden....................

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