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. Here’s an example of a book bundle:
| | { |
| | "name": "light reading", |
| | "books": [{ |
| | "id": "pg132", |
| | "title": "The Art of War" |
| | },{ |
| | "id": "pg2680", |
| | "title": "Meditations", |
| | },{ |
| | "id": "pg6456", |
| | "title": "Public Opinion" |
| | }] |
| | } |
The name field is a user-defined string to identify the list. Names do not have to be unique. The books field contains a list of the books in that bundle. Each book is identified by its document ID and includes the title of the book.
Our app will be called Better Book Bundle Builder (or B4 for short).
We’ll work extensively with the books index that we set up in Chapter 6, Commanding Databases, as well as a second Elasticsearch index called b4. The application will work roughly as follows:
It will communicate with two indices: the books index and the b4 index.
To the B4 application, the books index is read-only (we will not add, delete, or overwrite any documents in it).
The b4 index will store user data, including the book bundles that users make.
To create the b4 index, make sure Elasticsearch is running, then open a terminal to the esclu directory from the last chapter. Use esclu to create the b4 index:
| | $ ./esclu create-index -i b4 |
| | {"acknowledged":true,"shards_acknowledged":true} |
Now we’re ready to create our modular, RESTful web services.
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’ll specify some configuration parameters and then pull in API modules.
To start, create a directory called b4 to house the B4 project. Next, use npm init to create the basic outline of a package.json. All of the default values are fine.
| | $ mkdir b4 |
| | $ cd b4 |
| | $ npm init |
Now install the Express, Morgan, and nconf modules. We’ll use the nconf module to manage configuration settings like the hostname and port of the Elasticsearch server.
| | $ npm install --save --save-exact express@4.14.1 morgan@1.8.1 nconf@0.8.4 |
OK, next comes the configuration settings. Open your text editor and enter the following:
| | { |
| | "port": 60702, |
| | "es": { |
| | "host": "localhost", |
| | "port": 9200, |
| | "books_index": "books", |
| | "bundles_index": "b4" |
| | } |
| | } |
Save this file as config.json. It contains the port number that Express will listen on as well as information about where to find the Elasticsearch service.
Finally, let’s put together the shell of the server.js. Open your editor and enter this:
| | 'use strict'; |
| | const express = require('express'); |
| | const morgan = require('morgan'); |
| | const nconf = require('nconf'); |
| | const pkg = require('./package.json'); |
| | |
| | nconf.argv().env('__'); |
| | nconf.defaults({conf: `${__dirname}/config.json`}); |
| | nconf.file(nconf.get('conf')); |
| | |
| | const app = express(); |
| | |
| | app.use(morgan('dev')); |
| | |
| | app.get('/api/version', (req, res) => res.status(200).send(pkg.version)); |
| | |
| | app.listen(nconf.get('port'), () => console.log('Ready.')); |
Aside from the nconf setup, the content of this file should seem pretty familiar to you. Putting the nconf part aside for just a moment, let’s step through the rest of the code.
First, as usual, we pull in the modules we depend on. We’ll also pull in the package.json contents so we can put out a simple version endpoint.
After setting up nconf, we invoke express to create the app object. With it, we add Morgan for logging, then establish a simple endpoint at the path /api/version that reports the version listed in package.json.
Finally, we instruct the Express app to listen on our configured port (60702). Let’s start this up and try it out; then we’ll return to nconf.
| | $ npm start |
| | |
| | > b4@1.0.0 start ./code/web-services/b4 |
| | > node server.js |
| | |
| | Ready. |
To test it, use curl to hit the /api/version endpoint.
| | $ curl -s "http://localhost:60702/api/version" |
| | 1.0.0 |
Great! Looks like everything is working so far. Now let’s dig into how nconf manages configuration settings.
The nconf module manages configuration settings through a customizable hierarchy of config files, environment variables, and command-line arguments. The order in which you load a source of configuration determines its precedence. Earlier values stick, meaning that later values will not overwrite them. Here again is the first line of the server.js file that handles setting up nconf:
| | nconf.argv().env('__'); |
This first line means that nconf should load argument variables first, then environment variables. The double underscore string passed to env means that two underscores should be used to denote object hierarchy when reading from environment variables. This is because many shell programs do not allow colon characters in variable names.
An example should help clarify. In the config.json file, recall that we set es.host to localhost. nconf uses the colon character by default to flatten the object hierarchy, so to get the value of this configuration parameter, we would call nconf.get(’es:host’) in Node.
Now, we’ve set it up so that nconf gives us the option to override es:host either as an argument variable or as an environment variable since these are loaded first. To override es:host as a command-line argument, we would invoke server.js from the command line like this:
| | $ node server.js --es:host=some.other.host |
On the other hand, to override es:host with an environment variable, we would invoke server.js like so:
| | $ es__host=some.other.host node server.js |
Now let’s move on to the second line of the nconf setup code.
| | nconf.defaults({conf: `${__dirname}/config.json`}); |
This line establishes a default value for the conf parameter. This is the path to the configuration file we created earlier. But since we’re prioritizing environment variables and command-line arguments, the user could employ either of those mechanisms to override the configuration file path.
For example, to override the config file path using a command-line argument, you could do this:
| | $ node server.js --conf=/path/to/some.other.config.json |
Finally, in the last line of the nconf setup stanza, we tell nconf to load the file defined in the conf path.
| | nconf.file(nconf.get('conf')); |
Any values in that file will take effect only if they haven’t been set already in the command-line arguments or environment variables.
These three lines give you some pretty amazing flexibility right out of the gate. Of course, you could choose to call the argv, env, file, and other methods in a different order to achieve different effects. Check out the nconf npm page for more info.[67]