-
Notifications
You must be signed in to change notification settings - Fork 26
5.4. Tutorial 4 Call an API
What we want to achieve in this tutorial is simple: we want to call an external API to retrieve data. We are going to develop an IA which needs to call the "CityBikes" API. CityBikes provides a set of data about bike sharing in some cities.
First, we generate our IA. We won't cover that part, since we did it in the first two tutorials.
Our example is based on CityBikes.
To use an API, you must import the "API caller" provided by Qwant.
var apiCaller = require('../../api_caller');
The API caller allows us to check API permissions. Indeed, we reserve the right to blacklist a service that doesn't comply with our rules.
You will also need to import the Redis tools. Redis is an in-memory database that persists on the disk. We use it for caching API responses. We also need to initialize it, and declare a cache key.
const CACHE_KEY = 'citybikes-cache';
const redisTools = require('../../redis_tools');
redisTools.initRedis();
Next, you can call the API in your IA with the "call" function.
let data = apiCaller.call(api_request, structure, proxyURL, this.timeout, redisTools);
The "call" function takes 5 parameters:
-
api_request: Request URL in String format. To call an API with parameters you just have to concatenate them like this :
var api_request = "https://www.myapi.com/" + parameter1 + "?id=" + parameter2;
-
structure: It's the structure of the answer in json format :
{"field" : "format"}
. The structure provided must match exactly with the API's answer. If some fields are optional, it's necessary to add "@_" in front of the key like{"@_optionalField": "format"}
. For an array "[]" only the structure of the first element is necessary. Concerning the values, it is necessary to put their format in the value of the json key. The possible values are:[String, boolean, object, Array, number]
. If a value can have several types, simply concatenate them with a comma:{"field": "Array,String"}
. -
proxyURL: (if needed, can be
null
). - this.timeout: Integer containing the number of milliseconds to wait for a server to send response headers (and start the response body) before aborting the request. It is set in the IA's main file.
- redisTools: the redisTools object. A basic structure would look like this:
{
"fields1": "Number"
, "@_optional_fields": "Array,String"
, "fields2" : [{
"name": "String"
, "id": "Boolean"
}]
}
var Promise = require("bluebird");
var _ = require('@qwant/front-i18n')._;
module.exports = {
getData: function (values, proxyURL, language, i18n) {
const _ = i18n._;
return new Promise(function (resolve, reject) {
if (values && values[0]) {
// when generating our IA, we chose a "strict" trigger with "bikes" as the keyword.
// It means it will trigger the IA if the query is strictly equal to "bikes", and nothing else.
// So when using a "strict" trigger, you don't need to check values[2] as it doesn't exist.
// Declare redisTools
const CACHE_KEY = 'citybikes-cache';
const CACHE_EXPIRE = 7200;
const redisTools = require('../../redis_tools');
redisTools.initRedis();
// Checking the cache to avoid requesting the API
redisTools.getFromCache(CACHE_KEY).then((cached) => {
if (cached) { // we already have a recent answer
cached = JSON.parse(cached);
if (cached) {
cached.fromCache = true;
resolve(cached);
} else {
reject("Error: something went wrong while fetching cached response");
}
} else { // no cache
// Declares the API Caller
var apiCaller = require('../../api_caller');
// Your request
var api_request = 'https://api.citybik.es/v2/networks';
// Defines the structure of the answer
var structure = {
"networks": [
{
"company": "Array,String",
"href": "String",
"id": "String",
"@_gbfs_href": "String",
"@_license": {
"@_name": "String",
"@_url": "String"
},
"location": {
"city": "String",
"country": "String",
"latitude": "number",
"longitude": "number"
},
"name": "String"
}
]
};
// Call the API and get back data
apiCaller.call(api_request, structure, proxyURL, this.timeout, redisTools).then((apiRes) => {
redisTools.saveToCache(CACHE_KEY, apiRes, CACHE_EXPIRE);
apiRes.fromCache = false;
resolve(apiRes);
}).catch((error) => {
reject(error);
});
// Treat data
// ...
redisTools.saveToCache(CACHE_KEY, data, CACHE_EXPIRE);
data.fromCache = false;
resolve(data);
}
});
} else {
reject("Couldn't process query.")
}
});
},
...
};
{
"networks": [
{
"company": [
"Nextbike GmbH"
],
"href": "/v2/networks/norisbike-nurnberg",
"id": "norisbike-nurnberg",
"location": {
"city": "Nürnberg",
"country": "DE",
"latitude": 49.4479,
"longitude": 11.0814
},
"name": "NorisBike"
},
{
"company": [
"Nextbike GmbH"
],
"href": "/v2/networks/sz-bike-dresden",
"id": "sz-bike-dresden",
"location": {
"city": "Dresden",
"country": "DE",
"latitude": 51.0535,
"longitude": 13.7387
},
"name": "sz-bike"
},
...
]
}
If for some reason the API response is different from the structure you provided, or times out too many times, your API will be banned. This is done so to prevent unwanted behaviors once the IA goes to production.
If that happens, you will need to remove the API from the blacklist:
There are two ways to unban your API:
- delete the api from the blacklist database : http://localhost:3002/blacklist/remove/?host=api.citybik.es
- purge the entire blacklist database : http://localhost:3002/blacklist/purge