Create NodeJs module to work with third party Restful APIs
I work with Nodejs and often implement third party Restful APIs in the bitcoin exchange world. I have learned enough to see the pattern and make the implementation smoother every time. Here is what I got and I hope that this will reach someone with the same goal : less time researching and shorter trial-and-error period. Restful APIs can be public(unauthentic) or private (authentic), plus each third party will have their own rules of how to call their private API based on their choice of encrypted method. Good chance is, you can find some modules that is already done for you and it will be a piece of cake, but what if there is none, or what if you want to take it into your own hand and want to write your own module to ensure the security of your privacy?. This tutorial will present a way to write simple Node module that will allow you to use JSON RPC to call APIs, public and private. I assumes that you already know enough of Javascript, Nodejs and npm modules to be able to benefit from this tutorial. I use Yunbi exchange as an example for the APIs. Now, let’s start
-
Step 1 : Understand the third party’s API Most public API is just simple requests, but private APIs require much more in the options, let’s take a look at these private APIs requirements : So the request should look like this :
hash = HMAC-SHA256(‘GET|/api/v2/markets|access_key=xxx&foo=bar&tonce=123456789’, ‘yyy’) .to_hex = ‘someHex’
curl -X GET ‘https://yunbi.com/api/v2/markets?access_key=xxx&foo=bar&tonce=123456789&signature=hash’
When we write the module for this API, we have to make sure we know the correct hash to compile in order to have the result back. In the next step, I will demonstrate the functions in the node module that is written based on the above requirements.
-
Step 2 : Start writing the module
// Constructor function moduleName(key, secret) { // Generate headers signed by this user’s key and secret. // The secret is encapsulated and never exposed this._getPrivateHeaders = function (parameters, link) { var paramString, signature; var url = “POST|” + HASH_URL + link + “|” if (!key || !secret) { throw ‘Yunbi: Error. API key and secret required’; }
// Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` paramString = Object.keys(parameters).sort(parameters).map(function (param) { return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); }).join('&'); var signature = crypto.createHmac('SHA256', secret).update(url + paramString).digest('hex'); parameters.signature = signature signature = crypto.createHmac('sha256', secret).update(paramString).digest('hex'); //console.log("SIGNATURE " +signature) Gkey = key Gsign = signature return { Key: key, Sign: signature }; };
}
Next, we will create prototype of the module, which will include a request function, a private post and private get function.
ModuleName.prototype = {
constructor: Yunbi,
// Make an API request
_request: function (options, callback) {
request(options, function (err, response, body) {
// Empty response
if (!err && (typeof body === 'undefined' || body === null)) {
err = 'Empty response';
}
callback(err, body);
});
return this;
},
// Make a public API request
_public: function (link, parameters, callback) {
var options;
if (typeof parameters === 'function') {
callback = parameters;
parameters = {};
}
parameters || (parameters = {});
options = {
method: 'GET',
url: API_URL + link,
qs: parameters
};
return this._request(options, callback);
},
// Make a private API request POST
_privatePost: function (link, parameters, key, secret, callback) {
var options;
if (typeof parameters === 'function') {
callback = parameters;
parameters = {};
}
parameters || (parameters = {});
parameters.tonce = nonce() / 100;
parameters.access_key = key
var paramString = Object.keys(parameters).sort(parameters).map(function (param) {
return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]);
}).join('&');
options = {
method: "POST",
url: API_URL + link,
form: parameters,
headers: this._getPrivateHeaders(parameters, link)
};
options.headers['User-Agent'] = "Yunbi API Client/0.0.1"
return this._request(options, callback);
},
// Make a private API request GET
_privateGet: function (link, key, secret, callback) {
var options;
var url = "GET|" + HASH_URL + link + "|"
var parameters = {}
parameters.access_key = key
parameters.tonce = parseInt(nonce() / 100);
var paramString = Object.keys(parameters).sort(parameters).map(function (param) {
return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]);
}).join('&');
var signature = crypto.createHmac('sha256', secret).update(url + paramString).digest('hex');
parameters.signature = signature
paramString = paramString + "&" + encodeURIComponent('signature') + "=" + encodeURIComponent(signature)
options = {
method: 'GET',
url: API_URL + link + "?" + paramString
};
return this._request(options, callback);
},
Now we define the methods that are available for the users through this module
// PUBLIC METHODS
getTicker: function (A, B, callback) {
var parameters = {
market: joinCurrencies(A, B)
};
var url = '/tickers/' + joinCurrencies(A, B) + '.json'
return this._public(url, parameters, callback);
},
// PRIVATE METHODS
myBalances: function (key, secret, callback) {
return this._privateGet('/members/me.json', key, secret, callback);
},
-
Step 3 : Testing
- My personal way of testing with Nodejs and API is just functional unit tests : writing tests for each call, I find this way cost less time and efficient for testing production also in case you have different authentication for production and development environments. But again this is personal choice and I’m sure there are good testing Frameworks out there that I’m happy to hear if you have any suggestion.
-
Step 4 : Publish the module on npmjs
- When all tests are passed, happy hour is here : time to put your work out there and join (or continue to contribute) to the open source community, or put it to work for your company, projects … forgive me to assume that you also know how to publish your modules. If not, I (will) have another post for common path I go in working with node module, from writing to publishing and upgrading versions.