Skip to content
This repository was archived by the owner on Apr 6, 2020. It is now read-only.

5.4. Tutorial 4 Call an API

s.mombuleau edited this page Dec 4, 2018 · 2 revisions

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.

Import the caller

To use an API, you must import the "API caller" provided by Qwant.

app/src/modules/citybikes/citybikes.js
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.

Import Redis Tools

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.

app/src/modules/citybikes/citybikes.js
const CACHE_KEY = 'citybikes-cache';
const redisTools = require('../../redis_tools');
redisTools.initRedis();

Let's code

Next, you can call the API in your IA with the "call" function.

app/src/modules/citybikes/citybikes.js
    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"
   }]
}

Example

app/src/modules/citybikes/citybikes.js
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.")
                }
            });
        },
    ...
};

Typical CityBikes API response

https://api.citybik.es/v2/networks
{
  "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"
    },
    ...
  ]
}

Blacklist

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: