Calling a REST API from a NodeJS Script

RSS
Jonathan's picture by Jonathan

Lately I have been experimenting with using NodeJS as a scripting language for personal tasks, rather than its typical function as a web server. There are many things about NodeJS that I like, and writing these little disposable scripts has been a great way to experiment.

Recently I wanted to perform some batch operations with my content on The Game Crafter's web service. The Game Crafter provides a full REST API, so this was a clear way to add some automation to my life.

In particular, the service requires that its users approve each card in a deck before it is sent to the printer. This is a reasonable requirement, but for people like me who automate the creation of their graphics, it's a lot of manual clicking to go through each card in turn and approve it. Instead, I wanted to check and approve one card, and then automate the rest of the approvals for a deck.

For a start, in this article I'll talk about how I used NodeJS to retrieve a listing of card objects for the deck using the REST API. Later, I'll discuss actually taking action using these results.

Making REST calls

Each API call takes a set of data in JSON format and returns data also in JSON format. The exception to this is a GET request, which instead takes its data as a query string. I didn't want to have to deal with this detail, so I cobbled together a wrapper function that would produce data in the correct format and issue the request.

var querystring = require('querystring');
var https = require('https');

var host = 'www.thegamecrafter.com';
var username = 'JonBob';
var password = '*****';
var apiKey = '*****';
var sessionId = null;
var deckId = '68DC5A20-EE4F-11E2-A00C-0858C0D5C2ED';

function performRequest(endpoint, method, data, success) {
  var dataString = JSON.stringify(data);
  var headers = {};
  
  if (method == 'GET') {
    endpoint += '?' + querystring.stringify(data);
  }
  else {
    headers = {
      'Content-Type': 'application/json',
      'Content-Length': dataString.length
    };
  }
  var options = {
    host: host,
    path: endpoint,
    method: method,
    headers: headers
  };

  var req = https.request(options, function(res) {
    res.setEncoding('utf-8');

    var responseString = '';

    res.on('data', function(data) {
      responseString += data;
    });

    res.on('end', function() {
      console.log(responseString);
      var responseObject = JSON.parse(responseString);
      success(responseObject);
    });
  });

  req.write(dataString);
  req.end();
}

This function accepts an endpoint (the URL of the API call), the method to use (POST, GET, PUT, etc.), the data object, and a success callback. We'll walk through each bit of the function and discuss it.

var querystring = require('querystring');
var https = require('https');

var host = 'www.thegamecrafter.com';
var username = 'JonBob';
var password = '*****';
var apiKey = '*****';
var sessionId = null;
var deckId = '68DC5A20-EE4F-11E2-A00C-0858C0D5C2ED';

Since this is a simple, one-off script, We're not putting much care into the organization of my configuration. We're just defining configuration info in variables global to the script's scope.

Note that we are requiring the https library rather than http, just because that's the protocol this site uses for its transport. The syntax is mostly interchangeable.

var dataString = JSON.stringify(data);
var headers = {};

if (method == 'GET') {
  endpoint += '?' + querystring.stringify(data);
}
else {
  headers = {
    'Content-Type': 'application/json',
    'Content-Length': dataString.length
  };
}

This is where the data is encoded. We produce a JSON-formatted string from whatever data object was passed in. If the method is GET, then we modify the URL we're calling so that it contains the query string representation of the same data. The NodeJS querystring library makes this painless.

If the method isn't GET, then we will be passing it as JSON text, so we need to make sure the headers we send contain appropriate MIME info.

Next, we prepare the options to send on to the HTTPS call.

var options = {
  host: host,
  path: endpoint,
  method: method,
  headers: headers
};

Then, we need to set up the HTTPS request.

var req = https.request(options, function(res) {
  res.setEncoding('utf-8');

  var responseString = '';

  res.on('data', function(data) {
    responseString += data;
  });

  res.on('end', function() {
    console.log(responseString);
    var responseObject = JSON.parse(responseString);
    success(responseObject);
  });
});

Because this is NodeJS, all of the functionality is handled asynchronously; we need to set up handlers to accept incoming data, as well as to deal with the response once it's complete. In this case, incoming data is simple. We just have to accept the data and concatenate it onto a string. Once the transfer is done, we can take that string and parse the JSON back into JavaScript objects we can work with. Finally, we call the success callback with this response info.

req.write(dataString);
req.end();

Lastly, we need to provide the JSON data as the body of the request, and actually trigger the send. This completes the function.

Logging In and Taking Action

To actually use this performRequest() function, we'll need to call a couple of different REST methods. First, the API requires authentication, so we'll need a function that logs us in.

function login() {
  performRequest('/api/session', 'POST', {
    username: username,
    password: password,
    api_key_id: apiKey
  }, function(data) {
    sessionId = data.result.id;
    console.log('Logged in:', sessionId);
    getCards();
  });
}

The login() function simply calls the appropriate REST endpoint, passing the user name and password to the server. The server responds with a session ID, which we will need to pass into all further requests.

Next, we call a function that retrieves some data.

function getCards() {
  performRequest('/api/pokerdeck/' + deckId + '/cards', 'GET', {
    session_id: sessionId,
    "_items_per_page": 100
  }, function(data) {
    console.log('Fetched ' + data.result.paging.total_items + ' cards');
  });
}

This function will grab the object info for the cards in the deck. In a future installment, we will act on this data; for now, we simply log it to the console.

Finally, we need to bootstrap the whole process.

login();

The Output

We can call our script from the command line, and we get appropriate feedback from the server:

$ node batch_approval.js 
{
   "result" : {
      "object_type" : "session",
      "id" : "79D1A27A-FA36-11E2-A5D8-5AD5600103C4",
      "object_name" : "Session",
      "user_id" : "9C0B5E42-81AD-11E1-A004-09691B09C483"
   }
}

Logged in: 79D1A27A-FA36-11E2-A5D8-5AD5600103C4
{
   "result" : {
      "paging" : {
         "total_pages" : 1,
         "next_page_number" : 2,
         "total_items" : "45",
         "items_per_page" : "100",
         "previous_page_number" : 1,
         "page_number" : 1
      },
      "items" : [
         {
            "has_proofed_face" : "1",
            "back_id" : "541691A8-EE51-11E2-A5AE-FF57C0D5C2ED",
            "back_size" : "825x1125",
            "object_type" : "pokercard",
            "face_id" : "541691A8-EE51-11E2-A5AE-FF57C0D5C2ED",
            "date_created" : "2013-07-16 20:07:41",
            "face_size" : "825x1125",
            "deck_id" : "68DC5A20-EE4F-11E2-A00C-0858C0D5C2ED",
            "id" : "5F152E64-EE53-11E2-AB21-0858C0D5C2ED",
            "date_updated" : "2013-07-16 20:09:36",
            "can_edit" : 1,
            "name" : "1",
            "quantity" : "1",
            "edit_uri" : "/publish/game/A959464C-B3D3-11E1-BBA5-9818E208FE53/decks/pokerdecks/68DC5A20-EE4F-11E2-A00C-0858C0D5C2ED/cards",
            "has_proofed_back" : "1",
            "back_from" : "Deck",
            "object_name" : "Poker Card",
            "class_number" : "1"
         },
...

It works! The next step (in a future article) will be to iterate over these items and to do something with them.