Make HTTPS requests in Node.js


1 | The https module


The https module is a core Node.js module that enables us to send request by using the HTTPS protocol.


2 | The https.request() method


The https.request() method sends a request to a secure web server.


This method has 2 characteristics:


  1. It is a non-blocking event-based function. When https.request() is invoked, it emits at one point an event that represents the response of the HTTPS request. We are able to perform some action on this event by using a callback function. For sake of clarity, this event, is usually called response or res in the callback function, but we could name it anything else. This response or res event is a Readable stream (an instance of the class http.IncomingMessage, which is a subclass the class stream.Readable).
  2. This method returns a value: a Writable stream (an instance of the class http.ClientRequest, which is a subclass of the class stream.Writable.

In other words, this method sends a Writable Stream and it "receives" (emits) a Readable Stream.


This method has 2 parameters:



The "response" Readable Stream has in itself 2 characteristics:


  1. As a Readable Stream, it emits itself events, that we can listen to by calling the .on() or addListener() methods.
  2. This Readable Stream must be consumed. If it is not consumed, this can lead to a "process out of memory" error.

3 | Make a universal request


Let us see an example of a universe way to make requests. We can make GET, POST, PUT and DELETE request with the same function, but by pasing different options objects.


// Example 1 - Universal request

const https = require('https');

const universalRequest = (options, dataToSend = false) => {
  return new Promise(resolve => {
    const req = https.request(options, res => {
      if (res.statusCode < 200 || res.statusCode > 299) {
        resolve('ERROR: ' + res.statusCode);
        res.resume();
      } else {
        let bodyResponse = '';
        res.on('data', chunk => bodyResponse += chunk);
        res.on('end', () => {
          const result = JSON.parse(bodyResponse);
          if (result) {
            resolve(result);
          } else {
            resolve('ERROR: No data sent back');
          }
        });
      }
    });
    
    req.on('error', (_) => {
      resolve('ERROR: problem with request');
    });

    if (dataToSend) {
      req.write(dataToSend);
    }
    
    req.end();
  });
};

The previous code is quite dense. Let me comment it a bit.


The https module


First, we require the module https and assign its properties to the variable https.


The function universalRequest()


We define a function called universalRequest(). This function has 2 parameters:


  1. The parameter options, which is required by the method https.request() that we use within our universalRequest function. This object specifies the type of request that we want to make. The object will have different values depending on if it is a GET, a POST, a PUT or a DELETE request.
  2. The parameter dataToSend which has a default value of false if it is not passed to the function when it is called. The dataToSend parameter lets us specify what data we want to send for POST and PUT requests. This data is written into the Writable Stream that we send thanks to the code:

if (dataToSend) {
      req.write(dataToSend);
    }

The function universalRequest() returns a Promise, so we are able to perform actions on the returned Promise if need be. Note that we only resolve values, and we do not reject any error. Rejecting an error would make the code raise an error and stop the execution, which is not necessarily the best scenario when making requests. We will resolve special Strings that let us know that there were errors. If we have several request to make in our program, we do not want an error to interrupt the whole code.


The method https.request

We call the method https.request(). The return value of this method is the request that we send, i.e. a Writable Stream. We assign it to the variable req. Since req is a Writable Stream, we are able to register an event handler in case an error occurs. When an error occurs, a Writable Stream emits an 'error' event.

We write into the Writable Stream the data to send with the request req if need be with the code:

if (dataToSend) {
      req.write(dataToSend);
    }

Then, we signal to Node.js the end of the writing process by calling the method stream.Writable.protoype.end() on req. The request is ready to be sent.


The https.request() is passed 2 arguments:


  1. an options object.
  2. A callback function to process the response that we call res. First, we check the status of the response, by using the property statusCode of the response res. If it not a valid code (not in the 2 hundreds), then we resolve an error. But also have to consume the res Readable Stream to avoid memory issues. That is what we do with the code res.resume();. Otherwise, if the response is OK, we process it, and we register event handlers like any Readable Stream.

We can test our function with examples for each type of HTTP requests thanks to the website https://jsonplaceholder.typicode.com/. We log the responses of the requests by chaining then() methods to the Promise returned by the function universalRequest().


// Example 2

const https = require('https');

const universalRequest = (options, dataToSend = false) => {
  return new Promise(resolve => {
    const req = https.request(options, res => {
      if (res.statusCode < 200 || res.statusCode > 299) {
        resolve('ERROR: ' + res.statusCode);
        res.resume();
      } else {
        let bodyResponse = '';
        res.on('data', chunk => bodyResponse += chunk);
        res.on('end', () => {
          const result = JSON.parse(bodyResponse);
          if (result) {
            resolve(result);
          } else {
            resolve('ERROR: No data sent back');
          }
        });
      }
    });
    
    req.on('error', (_) => {
      resolve('ERROR: problem with request');
    });

    if (dataToSend) {
      req.write(dataToSend);
    }
    
    req.end();
  });
};

// Example of successful GET request
const getOptions_1 = {
  method: 'GET',
  hostname: 'jsonplaceholder.typicode.com',
  path: '/posts/10',
}

universalRequest(getOptions_1)
  .then(value => {
    console.log('Successful GET with universalRequest()');
    console.log(value);
    console.log('');
  });

// Example of failing GET request
const getOptions_2 = {
  method: 'GET',
  hostname: 'jsonplaceholder.typicode.com',
  path: '/posts/1000',
}

universalRequest(getOptions_2)
  .then(value => {
    console.log('Failing GET with universalRequest()');
    console.log(value);
    console.log('');
  });

// Example of POST request
const postData = JSON.stringify({
  title: '50 Cent',
  body: "Get Rich Or Die Tryin'",
  userId: 100,
});

const postOptions = {
  method: 'POST',
  hostname: 'jsonplaceholder.typicode.com',
  path: '/posts',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': postData.length
  }
};

universalRequest(postOptions, postData)
  .then(value => {
    console.log('POST with universalRequest()');
    console.log(value);
    console.log('');
  });

// Example of PUT request
const putData = JSON.stringify({
  id: 9,
  title: 'Rick Ross',
  body: "Deeper Than Rap",
  userId: 1,
});

const putOptions = {
  method: 'PUT',
  hostname: 'jsonplaceholder.typicode.com',
  path: '/posts/9',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': putData.length
  }
};

universalRequest(putOptions, putData)
  .then(value => {
    console.log('PUT with universalRequest()');
    console.log(value);
    console.log('');
  });

// Example of DELETE request
const deleteOptions = {
  method: 'DELETE',
  hostname: 'jsonplaceholder.typicode.com',
  path: '/posts/20',
};

universalRequest(deleteOptions)
  .then(value => {
    console.log('DELETE with universalRequest()');
    console.log(value);
    console.log('');
  });

4 | Make GET, POST, PUT and DELETE requests


We can also define more specialized functions, one for each type of HTTP request. We still use our handy universalRequest() function as a start.


// Example 3

const https = require('https');

const universalRequest = (options, dataToSend = false) => {
  return new Promise(resolve => {
    const req = https.request(options, res => {
      if (res.statusCode < 200 || res.statusCode > 299) {
        resolve('ERROR: ' + res.statusCode);
        res.resume();
      } else {
        let bodyResponse = '';
        res.on('data', chunk => bodyResponse += chunk);
        res.on('end', () => {
          const result = JSON.parse(bodyResponse);
          if (result) {
            resolve(result);
          } else {
            resolve('ERROR: No data sent back');
          }
        });
      }
    });
    
    req.on('error', (_) => {
      resolve('ERROR: problem with request');
    });

    if (dataToSend) {
      req.write(dataToSend);
    }
    
    req.end();
  });
};

const getRequest = (hostname, path) => {
  const getOptions = {
    method: 'GET',
    hostname,
    path,
  };

  return universalRequest(getOptions);
};

const postJsonRequest = (hostname, path, postData) => {
  const postOptions = {
    method: 'POST',
    hostname,
    path,
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': postData.length
    }
  };

  return universalRequest(postOptions, postData);
};

const putJsonRequest = (hostname, path, putData) => {
  const putOptions = {
    method: 'PUT',
    hostname,
    path,
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': putData.length
    }
  };

  return universalRequest(putOptions, putData);

};

const deleteRequest = (hostname, path) => {
  const deleteOptions = {
    method: 'DELETE',
    hostname,
    path,
  };

  return universalRequest(deleteOptions);
};

// Example of successful GET request
const getOptions_1 = {
  method: 'GET',
  hostname: 'jsonplaceholder.typicode.com',
  path: '/posts/10',
}

getRequest('jsonplaceholder.typicode.com', '/posts/10')
.then(value => {
  console.log('Successful GET with getRequest()');
  console.log(value);
  console.log('');
});

// Example of failing GET request
const getOptions_2 = {
  method: 'GET',
  hostname: 'jsonplaceholder.typicode.com',
  path: '/posts/1000',
}

getRequest('jsonplaceholder.typicode.com', '/posts/1000')
.then(value => {
  console.log('Failing GET with getRequest()');
  console.log(value);
  console.log('');
});

// Example of POST request
const postData = JSON.stringify({
  title: '50 Cent',
  body: "Get Rich Or Die Tryin'",
  userId: 100,
});

const postOptions = {
  method: 'POST',
  hostname: 'jsonplaceholder.typicode.com',
  path: '/posts',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': postData.length
  }
};

postJsonRequest('jsonplaceholder.typicode.com', '/posts', postData)
.then(value => {
  console.log('POST with postJsonRequest()');
  console.log(value);
  console.log('');
});

// Example of PUT request
const putData = JSON.stringify({
  id: 9,
  title: 'Rick Ross',
  body: "Deeper Than Rap",
  userId: 1,
});

const putOptions = {
  method: 'PUT',
  hostname: 'jsonplaceholder.typicode.com',
  path: '/posts/9',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': putData.length
  }
};
  
putJsonRequest('jsonplaceholder.typicode.com', '/posts/9', putData)
.then(value => {
  console.log('PUT with putJsonRequest()');
  console.log(value);
  console.log('');
});

// Example of DELETE request
const deleteOptions = {
  method: 'DELETE',
  hostname: 'jsonplaceholder.typicode.com',
  path: '/posts/20',
};

deleteRequest('jsonplaceholder.typicode.com', '/posts/20')
.then(value => {
  console.log('DELETE with deleteRequest()');
  console.log(value);
  console.log('');
});

Author: Dimitri Alamkan
Initial publication date:
Last updated: