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

Using request to Fetch JSON over HTTP

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.

Creating an Elasticsearch Index

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.

Listing Elasticsearch Indices

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.