Skip to main content

Use the Node.js HTTP Module to Make a Request

The ecosystem around making requests in Node.js applications is huge. With countless libraries available, it can be helpful to understand why they exist. This helps to improve your understanding of Node.js as a whole, and makes choosing an HTTP client easier.

In the first post in this series, we looked at creating servers using Node's httpmodule. In this post, we will explore making a request using the http.requestmethod and handling the response. This is the underlying component used by most, if not all, Node.js request libraries.

http.request basics

The request method is part of Node's built-in http module. This module handles much of the low-level functionality needed to create servers, receive requests, send responses, and keep connections open. The requestimplementation, like most core implementations, is rather verbose and harder to work with than the abstractions that many libraries implement. It is also event-driven, and relies on streams to handle data. This doesn't mean it isn't usable. In fact, many parts of it will look familiar to third-party libraries as they have drawn inspiration from it. Let's start with a basic request.

const http = require("http")

http
  .request(
    {
      hostname: "postman-echo.com",
      path: "/status/200"
    },
    res => {
      let data = ""

      res.on("data", d => {
        data += d
      })
      res.on("end", () => {
        console.log(data)
      })
    }
  )
  .end()

This code block makes a GET request to http://postman-echo.com/status/200 and logs the response to the console. The request method can take a variety of configuration options. In this example, we are passing it the hostname and path. We didn't set a method, because GET is the default. The callback takes the response—res in the example—which can listen for events that fire during the response.

This example focuses on two key events. The data event and the end event. Because the response comes as a readable stream, we need to assemble it. For stringified responses you can build a string. Alternately, it may be a good idea to push to an array and then use a buffer to assemble the result like we do in the createServer article.

Each time the data event fires we append to a string. Finally, when the endevent fires we log the result. At the end of the call, we chain the end() method. This is a required part of the request, and without it the API call will not fire.

Let's look at another implementation of the same code.

const http = require("http")

let options = new URL("https://postman-echo.com/status/200")

let myRequest = http.request(options, res => {
  // Same as previos example
  res.on('data' d=> {
    //...
  })
  //... etc
})

myRequest.on("error", console.error)
myRequest.end()

In this version of the code, we create a URL with our desired API endpoint. The request can now take this URL object as the first argument. We also assign the whole code block to myRequest. This gives myRequest the ability to control the request listeners rather than chaining them to the end.

You may be tempted to try and call myRequest(), but the action that fires the request is .end().

Shorthand requests with http.get

While http.request can handle all the major HTTP methods, GET has a dedicated shorthand method. It works exactly the same, except it accepts a url string, removes the need for .end(), and sets the method to GET. For example, our first example would look as follows with http.get:

const http = require("http")

http.get("https://postman-echo.com/status/200", res => {
  let data = ""

  res.on("data", d => {
    data += d
  })
  res.on("end", () => {
    console.log(data)
  })
})

While not a major difference, this syntax makes handling GET requests easier.

POSTing to an API

With common GET request handled, let's look at POST. The syntax is mostly the same.

const http = require("http")

let body = JSON.stringify({
  title: "Make a request with Node's http module"
})

let options = {
  hostname: "postman-echo.com",
  path: "/post",
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Content-Length": Buffer.byteLength(body)
  }
}

http
  .request(options, res => {
    let data = ""
    res.on("data", d => {
      data += d
    })
    res.on("end", () => {
      console.log(data)
    })
  })
  .on("error", console.error)
  .end(body)

First we need to set up the body and options. Since we want to send JSON, we stringify the object and set it to body. Next, configure options with the necessary properties and headers. Note that we are telling the server the size of our payload with the Content-Length header, as well as the type of data with Content-Type.

The http.request portion looks mostly the same as in the earlier examples. We chain an on("error") listener. We also pass the body into the end(). This could also be written as .write(body).end().

Things to watch out for

While the code examples above are average use-cases, there are a few quirks to be aware of. Here are a few best practices and tips for working with http.request.

HTTPS

In the examples we use http, but for secure connections requiring HTTPS, you can use the https module in the same way. It is compatible with all the functionality of http.

Watch for empty responses

Empty responses won't fire a data event. This means if your response.on('end')event expects something from the data listener, you may run into problems for some responses like those from redirects. Make sure to perform any checks before relying on data coming from the data listener.

Make requests from inside the server

The host property in the options object defaults to localhost. This is nice for quick local experiments and instances where you want to call a server from within itself. For example:


let server = http.createServer()

server.listen(3000, error => {
  http.request({
    port: 3000,
    path: "/endpoint"
  }, res => {
    // handle the response
  })
})

Using this technique, you can make calls upon the server from inside the listenmethod's callback.

Error handling

One of the earlier examples briefly shows error handling, but it is worth mentioning again. The request itself, not the response, can fire an error event. You can listen for it by chaining .on('error', (error) => {}) onto a request before calling .end() or if you have set the request to a variable, you can listen on the variable.

let call = http.request(options, handleResponse)

call.on("error", handleError)
call.end()

This is also a good use case for building a custom error type to handle specific responses.

Cancelling requests

The req.abort() method allows you to cancel requests if used before the connection completes. In our example where the request is named call, this would be call.abort().

Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...