Table of Contents for
Node.js 8 the Right Way

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Node.js 8 the Right Way by Jim Wilson Published by Pragmatic Bookshelf, 2018
  1. Title Page
  2. Node.js 8 the Right Way
  3. Node.js 8 the Right Way
  4. Node.js 8 the Right Way
  5. Node.js 8 the Right Way
  6.  Acknowledgments
  7.  Preface
  8. Why Node.js the Right Way?
  9. What’s in This Book
  10. What This Book Is Not
  11. Code Examples and Conventions
  12. Online Resources
  13. Part I. Getting Up to Speed on Node.js 8
  14. 1. Getting Started
  15. Thinking Beyond the web
  16. Node.js’s Niche
  17. How Node.js Applications Work
  18. Aspects of Node.js Development
  19. Installing Node.js
  20. 2. Wrangling the File System
  21. Programming for the Node.js Event Loop
  22. Spawning a Child Process
  23. Capturing Data from an EventEmitter
  24. Reading and Writing Files Asynchronously
  25. The Two Phases of a Node.js Program
  26. Wrapping Up
  27. 3. Networking with Sockets
  28. Listening for Socket Connections
  29. Implementing a Messaging Protocol
  30. Creating Socket Client Connections
  31. Testing Network Application Functionality
  32. Extending Core Classes in Custom Modules
  33. Developing Unit Tests with Mocha
  34. Wrapping Up
  35. 4. Connecting Robust Microservices
  36. Installing ØMQ
  37. Publishing and Subscribing to Messages
  38. Responding to Requests
  39. Routing and Dealing Messages
  40. Clustering Node.js Processes
  41. Pushing and Pulling Messages
  42. Wrapping Up
  43. Node.js 8 the Right Way
  44. Part II. Working with Data
  45. 5. Transforming Data and Testing Continuously
  46. Procuring External Data
  47. Behavior-Driven Development with Mocha and Chai
  48. Extracting Data from XML with Cheerio
  49. Processing Data Files Sequentially
  50. Debugging Tests with Chrome DevTools
  51. Wrapping Up
  52. 6. Commanding Databases
  53. Introducing Elasticsearch
  54. Creating a Command-Line Program in Node.js with Commander
  55. Using request to Fetch JSON over HTTP
  56. Shaping JSON with jq
  57. Inserting Elasticsearch Documents in Bulk
  58. Implementing an Elasticsearch Query Command
  59. Wrapping Up
  60. Node.js 8 the Right Way
  61. Part III. Creating an Application from the Ground Up
  62. 7. Developing RESTful Web Services
  63. Advantages of Express
  64. Serving APIs with Express
  65. Writing Modular Express Services
  66. Keeping Services Running with nodemon
  67. Adding Search APIs
  68. Simplifying Code Flows with Promises
  69. Manipulating Documents RESTfully
  70. Emulating Synchronous Style with async and await
  71. Providing an Async Handler Function to Express
  72. Wrapping Up
  73. 8. Creating a Beautiful User Experience
  74. Getting Started with webpack
  75. Generating Your First webpack Bundle
  76. Sprucing Up Your UI with Bootstrap
  77. Bringing in Bootstrap JavaScript and jQuery
  78. Transpiling with TypeScript
  79. Templating HTML with Handlebars
  80. Implementing hashChange Navigation
  81. Listing Objects in a View
  82. Saving Data with a Form
  83. Wrapping Up
  84. 9. Fortifying Your Application
  85. Setting Up the Initial Project
  86. Managing User Sessions in Express
  87. Adding Authentication UI Elements
  88. Setting Up Passport
  89. Authenticating with Facebook, Twitter, and Google
  90. Composing an Express Router
  91. Bringing in the Book Bundle UI
  92. Serving in Production
  93. Wrapping Up
  94. Node.js 8 the Right Way
  95. 10. BONUS: Developing Flows with Node-RED
  96. Setting Up Node-RED
  97. Securing Node-RED
  98. Developing a Node-RED Flow
  99. Creating HTTP APIs with Node-RED
  100. Handling Errors in Node-RED Flows
  101. Wrapping Up
  102. A1. Setting Up Angular
  103. A2. Setting Up React
  104. Node.js 8 the Right Way

Implementing an Elasticsearch Query Command

Now that the documents are in the index, we can start querying for them. First we’ll take a look around using the existing get command, and then we’ll implement a specific command just for querying.

The Elasticsearch API endpoint we want to hit is /_search. We can already hit this endpoint using the get command. Let’s try that now.

 $ ​​./esclu​​ ​​get​​ ​​'_search'​​ ​​|​​ ​​jq​​ ​​'.'​​ ​​|​​ ​​head​​ ​​-n​​ ​​20
 {
  "took": 3,
  "timed_out": false,
  "_shards": {
  "total": 5,
  "successful": 5,
  "failed": 0
  },
  "hits": {
  "total": 53212,
  "max_score": 1,
  "hits": [
  {
  "_index": "books",
  "_type": "book",
  "_id": "pg100",
  "_score": 1,
  "_source": {
  "id": 100,
  "title": "The Complete Works of William Shakespeare",

Peeking at the head of the JSON response gives a good idea of what’s in it. As with the bulk API response, we see a took field, which indicates how long the request took to execute in milliseconds.

The results of the query are in the hits object, which contains three fields: total, max_score, and hits. The total field shows that all documents matched the query (we’ll perform more specific queries in a bit). The max_score field indicates the score value of the highest-scoring match. And the internal hits key points to an array of individual results.

Note that by default the _search API will return only the top 10 results. This can be increased by specifying the size URL parameter.

To dig into the results further, let’s again use the handy command-line tool jq.

Digging into Elasticsearch Results with jq

Using jq, we can dig down into the hits result from a query and see that the _source object is the original document. Try out the following command, which contains a fairly deep filter expression.

 $ ​​./esclu​​ ​​get​​ ​​'_search'​​ ​​|​​ ​​jq​​ ​​'.hits.hits[]._source'​​ ​​|​​ ​​head​​ ​​-n​​ ​​20
 {
  "id": 100,
  "title": "The Complete Works of William Shakespeare",
  "authors": [
  "Shakespeare, William"
  ],
  "subjects": [
  "English drama -- Early modern and Elizabethan, 1500-1600"
  ]
 }
 {
  "id": 1000,
  "title": "La Divina Commedia di Dante: Complete",
  "authors": [
  "Dante Alighieri"
  ],
  "subjects": []
 }
 {
  "id": 10000,

Here, the jq expression .hits.hits[]._source is a compact way of describing the following steps:

  • .hits—Start at the root object and traverse into the object under the key hits.
  • .hits—Do this again.
  • []—Return each element of the array individually.
  • ._source—For each element, return the object under the key _source.

The output is a stream of JSON objects, one after the other. To get a true JSON array instead, we can wrap the whole jq expression in brackets.

 $ ​​./esclu​​ ​​get​​ ​​'_search'​​ ​​|​​ ​​jq​​ ​​'[ .hits.hits[]._source ]'​​ ​​|​​ ​​head​​ ​​-n​​ ​​20
 [
  {
  "id": 100,
  "title": "The Complete Works of William Shakespeare",
  "authors": [
  "Shakespeare, William"
  ],
  "subjects": [
  "English drama -- Early modern and Elizabethan, 1500-1600"
  ]
  },
  {
  "id": 1000,
  "title": "La Divina Commedia di Dante: Complete",
  "authors": [
  "Dante Alighieri"
  ],
  "subjects": []
  },
  {

Through the _search API, if you pass a query parameter, q, Elasticsearch will use its value to find documents. For example, say we were interested in books by Mark Twain. We could search for documents whose authors array includes the substring Twain using the query expression authors:Twain, like this:

 $ ​​./esclu​​ ​​get​​ ​​'_search/?q=authors:Twain'​​ ​​|​​ ​​jq​​ ​​'.'​​ ​​|​​ ​​head​​ ​​-n​​ ​​30
 {
  "took": 3,
  "timed_out": false,
  "_shards": {
  "total": 5,
  "successful": 5,
  "failed": 0
  },
  "hits": {
  "total": 229,
  "max_score": 6.302847,
  "hits": [
  {
  "_index": "books",
  "_type": "book",
  "_id": "pg1837",
  "_score": 6.302847,
  "_source": {
  "id": 1837,
  "title": "The Prince and the Pauper",
  "authors": [
  "Twain, Mark"
  ],
  "subjects": [
  "London (England) -- Fiction",
  "Historical fiction",
  "Boys -- Fiction",
  "Poor children -- Fiction",
  "Social classes -- Fiction",
  "Impostors and imposture -- Fiction",

Elasticsearch’s query string syntax is a DSL with many useful features like wildcards, Boolean AND/OR operators, negation, and even regular expressions. We’ll explore some of these in future chapters, but a full treatment is outside the scope of this book. You can read about these options on Elasticsearch’s query string syntax page.[61]

Sometimes when you query Elasticsearch, you may not be interested in retrieving the whole source document for each match. Continuing the previous example, say we wanted to find just the title of each book by Mark Twain. For this, Elasticsearch supports another DSL for specifying a source filter.[62] Here we could use the source filter expression _source=title.

 $ ​​./esclu​​ ​​get​​ ​​'_search?q=authors:Twain&_source=title'​​ ​​|​​ ​​jq​​ ​​'.'​​ ​​|​​ ​​head​​ ​​-n​​ ​​30
 {
  "took": 2,
  "timed_out": false,
  "_shards": {
  "total": 5,
  "successful": 5,
  "failed": 0
  },
  "hits": {
  "total": 229,
  "max_score": 6.302847,
  "hits": [
  {
  "_index": "books",
  "_type": "book",
  "_id": "pg1837",
  "_score": 6.302847,
  "_source": {
  "title": "The Prince and the Pauper"
  }
  },
  {
  "_index": "books",
  "_type": "book",
  "_id": "pg19987",
  "_score": 6.302847,
  "_source": {
  "title": "Chapters from My Autobiography"
  }
  },

Now the _source objects contain only the title key. As you might imagine, this pairs well again with jq, which lets you extract just those specific strings.

(Note the trailing backslash to indicate a continuing line.)

 $ ​​./esclu​​ ​​get​​ ​​'_search?q=authors:Twain&_source=title'​​ ​​|​​ ​​\
  ​​jq​​ ​​'.hits.hits[]._source.title'
 "The Prince and the Pauper"
 "Chapters from My Autobiography"
 "The Awful German Language"
 "Personal Recollections of Joan of Arc — Volume 1"
 "Personal Recollections of Joan of Arc — Volume 2"
 "In Defence of Harriet Shelley"
 "The Innocents Abroad"
 "The Mysterious Stranger, and Other Stories"
 "The Curious Republic of Gondour, and Other Whimsical Sketches"
 "Jenkkejä maailmalla I\nHeidän toivioretkensä Pyhälle Maalle"

Now that we’ve covered a bit of what you can do with the _search API, let’s add a final command to esclu to issue these kinds of queries.

Implementing an Elasticsearch Query Command

Using your understanding of Elasticsearch’s _search API endpoint, it’s time to add one final command to esclu before closing out the chapter. The command will be called query, with the alias q for short. It will take any number of optional query parts, so the user won’t have to wrap the query in quotes.

Here’s an example usage of the q command that we’ll be adding:

 $ ​​./esclu​​ ​​q​​ ​​authors:Twain​​ ​​AND​​ ​​subjects:children

As before, we’ll want it to understand the --index flag to limit the query to a particular index. But additionally, it would be nice if we could specify an optional source filter expression to limit the output documents.

Begin by adding the --filter option alongside the other options during the initial program setup stanza toward the top of the file.

 program
 // Other options...
  .option(​'-f, --filter <filter>'​, ​'source filter for query results'​);

Finally, add this new query command just before the program.parse line at the bottom.

 program
  .command(​'query [queries...]'​)
  .alias(​'q'​)
  .description(​'perform an Elasticsearch query'​)
  .action((queries = []) => {
 const​ options = {
  url: fullUrl(​'_search'​),
  json: program.json,
  qs: {},
  };
 
 if​ (queries && queries.length) {
  options.qs.q = queries.join(​' '​);
  }
 
 if​ (program.filter) {
  options.qs._source = program.filter;
  }
 
  request(options, handleResponse);
  });

This code sets up a query command in much the same fashion as other commands you’ve created while going through this chapter. The parameter declaration [queries...] tells Commander that we expect any number of arguments to this command (even zero).

Inside the action callback, virtually all of the work focuses on building out the query string for the URL by adding properties to options.qs. request will take the properties of options.qs and encode them into query string parameters to append to the URL.

If the user provided any query parameters on the command line (using the -q flag), then we would concatenate them with spaces and assign the result to the q parameter. For example, if the user entered esclu -q "Mark" "Twain", then the q parameter would become the string "Mark Twain". When request encodes options.qs, this would become ?q=Mark%20Twain, appended to the end of the URL. Elasticsearch would then use this q parameter to search for matching documents. Likewise, if the user provided a filter on the command line with the -f flag, we would convert this to a _source parameter for the query string.

Now, continuing with the previous example, if the user only wanted to get back the title field of matching documents, then the thing to do is add -f title to the command line. request would then encode both options together as ?q=Mark%20Twain&_source=title.

After you save the file, give the new query command a try. The simplest query is the empty query, which matches all documents.

 $ ​​./esclu​​ ​​q​​ ​​|​​ ​​jq​​ ​​'.'​​ ​​|​​ ​​head​​ ​​-n​​ ​​30
 {
  "took": 4,
  "timed_out": false,
  "_shards": {
  "total": 5,
  "successful": 5,
  "failed": 0
  },
  "hits": {
  "total": 53212,
  "max_score": 1,
  "hits": [
  {
  "_index": "books",
  "_type": "book",
  "_id": "pg100",
  "_score": 1,
  "_source": {
  "id": 100,
  "title": "The Complete Works of William Shakespeare",
  "authors": [
  "Shakespeare, William"
  ],
  "subjects": [
  "English drama -- Early modern and Elizabethan, 1500-1600"
  ]
  }
  },
  {
  "_index": "books",

To abbreviate output, we can focus on just the title and author fields.

 $ ​​./esclu​​ ​​q​​ ​​-f​​ ​​title,authors​​ ​​|​​ ​​jq​​ ​​'.'​​ ​​|​​ ​​head​​ ​​-n​​ ​​30
 {
  "took": 5,
  "timed_out": false,
  "_shards": {
  "total": 5,
  "successful": 5,
  "failed": 0
  },
  "hits": {
  "total": 53212,
  "max_score": 1,
  "hits": [
  {
  "_index": "books",
  "_type": "book",
  "_id": "pg100",
  "_score": 1,
  "_source": {
  "title": "The Complete Works of William Shakespeare",
  "authors": [
  "Shakespeare, William"
  ]
  }
  },
  {
  "_index": "books",
  "_type": "book",
  "_id": "pg1000",
  "_score": 1,
  "_source": {

Now using jq we can focus on just the source objects.

 $ ​​./esclu​​ ​​q​​ ​​-f​​ ​​title,authors​​ ​​|​​ ​​jq​​ ​​'.hits.hits[]._source'​​ ​​|​​ ​​head​​ ​​-n​​ ​​30
 {
  "title": "The Complete Works of William Shakespeare",
  "authors": [
  "Shakespeare, William"
  ]
 }
 {
  "title": "La Divina Commedia di Dante: Complete",
  "authors": [
  "Dante Alighieri"
  ]
 }
 {
  "title": "The Magna Carta",
  "authors": [
  "Anonymous"
  ]
 }
 {
  "title": "My First Years as a Frenchwoman, 1876-1879",
  "authors": [
  "Waddington, Mary King"
  ]
 }
 {
  "title": "A Voyage to the Moon\r\nWith Some Account of the Manners and ...
  "authors": [
  "Tucker, George"
  ]
 }

Taking advantage of the joining of query parts, you can specify a complex query without wrapping it in quotes.

 $ ​​./esclu​​ ​​q​​ ​​authors:Shakespeare​​ ​​AND​​ ​​subjects:Drama​​ ​​-f​​ ​​title​​ ​​|\
 jq​​ ​​'.hits.hits[]._source.title'
 "The Tragedy of Othello, Moor of Venice"
 "The Tragedy of Romeo and Juliet"
 "The Tempest"
 "The Comedy of Errors"
 "Othello"
 "As You Like It"
 "The Two Gentlemen of Verona"
 "The Merchant of Venice"
 "Two Gentlemen of Verona"
 "All's Well That Ends Well"

Be aware that if you need any characters that your shell treats as special, you should wrap the whole query in quotes. For example, to do a multiword query with Elasticsearch you can wrap the expression in double quotes (q title:"United States"). But your shell may strip out these quotes unless they’re wrapped in another set of quotes (q ’title:"United States"’).