The Request module aims to simplify making HTTP requests, particularly when it comes to streaming data. It’s true that Node.js ships with a built-in module called http, which has APIs for creating low-level HTTP servers and making client requests. But using the Request module cuts past many of the details so you can focus on the job being done.
As an introduction to using the Request module, open your text editor to your index.js file and insert the following code after the url command.
| | program |
| | .command('get [path]') |
| | .description('perform an HTTP GET request for path (default is /)') |
| | .action((path = '/') => { |
| | const options = { |
| | url: fullUrl(path), |
| | json: program.json, |
| | }; |
| | request(options, (err, res, body) => { |
| | if (program.json) { |
| | console.log(JSON.stringify(err || body)); |
| | } else { |
| | if (err) throw err; |
| | console.log(body); |
| | } |
| | }); |
| | }); |
Like the url command from the previous section, this code adds a command called get that takes a single optional parameter called path. Inside the action callback, we’re using the request function provided by the module of the same name to execute an asynchronous HTTP request and call a callback when it’s done.
The options for request can include many settings, but here we’re providing only the URL and the Boolean flag json. When set to true, the json flag indicates that the request should include an HTTP header asking the server for a JSON-formatted response. It also ensures that the returned content will be parsed as JSON.
The callback function to request takes three parameters: the error if any, the response object, and the response body. If esclu was invoked with the --json flag, then we want to output the JSON stringified error or response body. Without the JSON flag, we want to throw an exception on error and otherwise output the response verbatim.
Once you save the file, head back to the terminal and we’ll give the get command a try. To start, run get without specifying a path, and without any other flags to give us a baseline.
| | $ ./esclu get |
| | { |
| | "name" : "kAh7Q7Z", |
| | "cluster_name" : "elasticsearch", |
| | "cluster_uuid" : "_kRYwEXISDKtcPXeihPwZw", |
| | "version" : { |
| | "number" : "5.2.2", |
| | "build_hash" : "f9d9b74", |
| | "build_date" : "2017-02-24T17:26:45.835Z", |
| | "build_snapshot" : false, |
| | "lucene_version" : "6.4.1" |
| | }, |
| | "tagline" : "You Know, for Search" |
| | } |
Recall that the default path is the root of the server: /. This response tells us that Elasticsearch is up and running, and gives us some additional information about the current running version.
If Elasticsearch is not up and running, you’ll see something quite different:
| | $ ./esclu get |
| | ./code/esclu/esclu:51 |
| | if (err) throw err; |
| | ^ |
| | |
| | Error: connect ECONNREFUSED 127.0.0.1:9200 |
| | at Object.exports._errnoException (util.js:1022:11) |
| | at exports._exceptionWithHostPort (util.js:1045:20) |
| | at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1090:14) |
Presuming your Elasticsearch is up and responding, you can start poking around to get more info. For example, the _cat endpoint offers a human-readable (non-JSON) API for assessing the health and status of your cluster. Start with get ’_cat’ for a list of options.
| | $ ./esclu get '_cat' |
| | =^.^= |
| | /_cat/pending_tasks |
| | /_cat/snapshots/{repository} |
| | /_cat/templates |
| | /_cat/health |
| | /_cat/segments |
| | /_cat/segments/{index} |
| | /_cat/aliases |
| | /_cat/aliases/{alias} |
| | /_cat/repositories |
| | /_cat/allocation |
| | /_cat/indices |
| | /_cat/indices/{index} |
| | /_cat/shards |
| | /_cat/shards/{index} |
| | /_cat/thread_pool |
| | /_cat/thread_pool/{thread_pools}/_cat/plugins |
| | /_cat/nodeattrs |
| | /_cat/tasks |
| | /_cat/count |
| | /_cat/count/{index} |
| | /_cat/fielddata |
| | /_cat/fielddata/{fields} |
| | /_cat/master |
| | /_cat/recovery |
| | /_cat/recovery/{index} |
| | /_cat/nodes |
Since we haven’t inserted any documents yet—or even created any indexes, for that matter—the responses to these commands will be pretty boring at this point. But keep in mind that you can use get with the _cat API to explore your cluster later.
Speaking of adding an index, let’s add a command to do that next.
To create an index, Elasticsearch expects an incoming HTTP PUT request to the index you wish to create.
Open your text editor and fill in the action callback as follows:
| | const handleResponse = (err, res, body) => { |
| | if (program.json) { |
| | console.log(JSON.stringify(err || body)); |
| | } else { |
| | if (err) throw err; |
| | console.log(body); |
| | } |
| | }; |
| | |
| | program |
| | .command('create-index') |
| | .description('create an index') |
| | .action(() => { |
| | if (!program.index) { |
| | const msg = 'No index specified! Use --index <name>'; |
| | if (!program.json) throw Error(msg); |
| | console.log(JSON.stringify({error: msg})); |
| | return; |
| | } |
| | |
| | request.put(fullUrl(), handleResponse); |
| | }); |
First, let’s factor out the handling of the response to the HTTP request. The way we handled the request for the get command is common to the rest of the requests we’ll be making, so it makes sense to turn this into a named function.
Next, take a look at the create-index command. Inside the action callback, we start by checking that the user specified an index with the --index flag. If not, we’ll either throw an exception or output an error as JSON if the --json flag is in use.
Then we use request.put to issue an HTTP PUT request using the handler we just defined.
You may already be familiar with the HTTP method POST, which is widely used for submitting HTML forms. The difference between POST and PUT is subtle but important. In cases where you know the full URL for the RESTful thing you’re working on, use PUT; otherwise, use POST. So if you’re updating an existing resource, PUT is always correct, but to create a new resource you use PUT only if you know the full URL to where that resource will be.
In the case of Elasticsearch, each index lives at a unique URL specified by its name under the server root. So to create a new index, we use PUT, pointing to the path. For the books index, the URL will be http://localhost:9200/books/. The fullUrl function we created earlier handles this URL construction already.
Save this file, then run esclu with no command-line arguments to confirm that create-index shows up.
| | $ ./esclu |
| | |
| | Usage: esclu [options] <command> [...] |
| | |
| | |
| | Commands: |
| | |
| | url [path] generate the URL for the options and path (default is /) |
| | get [path] perform an HTTP GET request for path (default is /) |
| | create-index create an index |
Great! Now let’s try to create the books index using the create-index command.
| | $ ./esclu create-index --index books |
| | {"acknowledged":true,"shards_acknowledged":true} |
It looks like Elasticsearch acknowledged the request. To see if the index is there, let’s list the indices using _cat/indices?v. The v stands for verbose, and adds a header row to the output table.
| | $ ./esclu get '_cat/indices?v' |
| | health status index uuid pri rep docs.count store.size pri.store.size |
| | yellow open books n9...sQ 5 1 0 650b 650b |
(Some values were omitted or truncated for brevity.)
Listing indices seems like a useful command, so let’s add it to esclu.
Insert the following code after the create-index command.
| | program |
| | .command('list-indices') |
| | .alias('li') |
| | .description('get a list of indices in this cluster') |
| | .action(() => { |
| | const path = program.json ? '_all' : '_cat/indices?v'; |
| | request({url: fullUrl(path), json: program.json}, handleResponse); |
| | }); |
As before, we use the program object to add a command, but this time we’re adding an alias using the alias method. This will allow us to invoke the list-indices command by typing only li instead of the full command name.
Inside the action callback method, first we determine the path to request. This will be _all if the user has specified JSON mode with the --json flag; otherwise it’ll be_cat/indices?v like we just used with the get command.
The question mark and colon (?:) constitute the ternary operator. This is an inline if statement that evaluates the first parameter then returns the second parameter if the first is true; otherwise it returns the third parameter. For example, if a and b are two numbers, you could find the greater of the two with a > b ? a : b.
Once we have the path, we run its full URL through request as before. Rather than setting up a separate options object to house the url and json properties, this time we’re specifying it inline.
Let’s try it out. Back on the command line, run esclu with the list-indices command (or li for short). You should see the same table as before.
| | $ ./esclu li |
| | health status index uuid pri rep docs.count store.size pri.store.size |
| | yellow open books n9...sQ 5 1 0 650b 650b |
Now let’s try it with the --json or -j flag.
| | $ ./esclu li -j |
| | {"books":{"aliases":{},"mappings":{},"settings":{"index":{"creation_date":"1484 |
| | 650920414","number_of_shards":"5","number_of_replicas":"1","uuid":"3t4pwCBmTwyV |
| | KMe_0j26kg","version":{"created":"5010199"},"provided_name":"books"}}}} |
When you GET /_all, Elasticsearch returns an object whose keys are the names of the indices, and values contain information about each index. The JSON contains some useful information, but is difficult to read on the command line. This is a good time to introduce a useful command-line tool for manipulating JSON, called jq.