1

Creating a simple Node.Js app where need to display data from two APIs, where both APIs returns multiple objects with an ID.

Need to display data from both these APIs on a single page, and somehow need to fetch the data from the two API based on the ID.

API 1 response looks like this:

    {
    "hikes": [
        {
            "id": 1,
            "active": true,
            "name": "Mt. Everest",          
        },
        {
            "id": 2,
            "active": true,
            "name": "K2",          
        },
        {
            "id": 3,
            "active": true,
            "name": "Mt. Kinley",          
        },
    ]
}

API 2 response looks like this:

{
    "hikes": [
        {
            "id": 1,
            "slots": 50,
            "available": 23,          
        },
        {
            "id": 2,
            "slots": 20,
            "available": 1,          
        },
        {
            "id": 3,
            "slots": 43,
            "available": 20,          
        },
    ]
}

Need to pull both APIs, fetch the data and render on a page to display "name", "slots", and "available".

This far managed to pull one of the APIs, and pass the data to a rendered index.ejs page, but I am not sure how I should pull the second API and some how fetch the data`s.

My code at the moment looks like this:

var port    = process.env.PORT || 3000,
    express = require("express"),
    request = require("request"),
    app = express();

app.set("view engine", "ejs");





var hikes = {
    url: "https://api.com/hikes",
    headers: {
      'Identifier': identifier
    }
  };

  var availability = {
    url: "https://api.com/hikes",
    headers: {
      'Identifier': identifier
    }
  };


app.get("/", function(req, res){


      function callback(error, response, body){
          if(!error && response.statusCode == 200){
              var data = JSON.parse(body);
              res.render("index", {data: data});
              })
          }
      }
    request(hikes, callback);
});


app.listen(port, function(){
    console.log("Running");
});

In my index.ejs, I for now have created a simple loop to print the names:

<% data["hikes"].forEach(function(hike){ %>

    <p><%= hike.name %></p>

<% }) %>

Any ideas on how to solve this?

Thanks!

4 Answers 4

1

If I understood correctly, I assume you are trying to fetch data from two APIs and want to merge the data into single array of objects based on object id and pass it to view. if this is the case then you can use https://www.npmjs.com/package/async to fetch data parallel from both the APIs then merge the data in to one array of objects and pass it to your view. Following code will help you understand the implementation.

var port    = process.env.PORT || 3000,
express = require("express"),
request = require("request"),
app = express();

var async = require('async');


app.set("view engine", "ejs");


var hikes = {
  url: "https://api.com/hikes",
  headers: {
   'Identifier': identifier
  }
};

var availability = {
  url: "https://api.com/hikes",
  headers: {
    'Identifier': identifier
  }
};


app.get("/", function(req, res) {
  function callback(error, response, body, cb) {
    if(error || response.statusCode != 200)
      return cb(true);

    cb(null, JSON.parse(body).hikes);//instead of sending data directly to view, send it to async callback to merge it latter
  }

  var tasks = { // tasks to run in parallel
    hikes: function (cb) {
      request(hikes, function (error, response, body) {
        callback(error, response, body, cb);
      });
    },
    availability: function (cb) {
      request(availability, function (error, response, body) {
        callback(error, response, body, cb);
      });
    }
  };

  async.parallel(tasks, function (err, resp) {
    if(err) {
      //handle error here, the error could be caused by any of the tasks.
      return;
    }

    var availabilityIdMap = resp.availability.map(function (availability) { return availability.id; });//get an array of all the availability ids
    var data = resp.hikes.map(function (hike) { //merging hike to corresponding availability object
      var availabilityIndex = availabilityIdMap.indexOf(hike.id); // finding the availability against the hike id.
      if(availabilityIndex < 0) //availability not found, just return hike
        return hike;

      var matchingAvailabilityObj = resp.availability[availabilityIndex]; //get the matching availability object
      var mergedObj = Object.assign(hike, matchingAvailabilityObj); //merge both objects
      return mergedObj;
    });

    // now the data will have an array of merged object with properties from hike and availability objects
    res.render("index", {data: data});
  });
});


app.listen(port, function(){
  console.log("Running");
});
Sign up to request clarification or add additional context in comments.

3 Comments

Thans alot, this looks pretty close. I get an error 'resp.availability.map is not a function. I also see that I wrote a typo in my initial post, the both the API returns with "hikes" first, not "hike" and "availability" - may that be related to the problem?
No, that was not the issue. Actually your API's are returning a JSON object containing attribute hikes. and I was assuming the API response will be an array, thats why there was error. Now I've done changes in our callback function for our APIs. function callback(error, response, body, cb) { if(error || response.statusCode != 200) return cb(true); cb(null, JSON.parse(body).hikes);//instead of returning the whole object, now I'm only returning hikes array from the object. }
did it work for you? If it did, please mark it as answer.
0

There are two possible options for creating your page:

  • cache answers from both apis
  • proxy requests to them

You need chose what will you use.

For cache use setInterval to store the answer into two variables objects each 5/60/N seconds.

For proxy use async/await approach and Promise.all to continue work after your have both answers. In this case I propose change request package to got

Comments

0

I would request you to read about Promise, and asynchronous function more for better understanding and solution.

For now this will work for you:

var hikes = {
    url: "https://api.com/hikes",
    headers: {
      'Identifier': identifier
    }
};

var availability = {
    url: "https://api.com/hikes",
    headers: {
      'Identifier': identifier
    }
};


app.get("/", function(req, res){


    function callback(error, response, body){
        if(!error && response.statusCode == 200){

            var data = JSON.parse(body);

            request(availability, (err, response, body) => {

                if(!err && response.statusCode == 200){

                    var data2 = JSON.parse(body);

                    res.render("index", {data1: data, data2: data2});

                }

            });

        }
    }
    request(hikes, callback);
});

index.ejs:

<% data1["hikes"].forEach(function(hike){ %>

    <p><%= hike.name %></p>

<% }) %>

<% data2["availability"].forEach(function(available){ %>

    <p><%= available.slots %></p>
    <p><%= available.available %></p>

<% }) %>

Better Solution

function apiCall (reqOps) {
    return new Promise ( (resolve, reject) => {

        request(reqOps, (err, res, body) => {

            if(!error && response.statusCode == 200){
                resolve( JSON.parse(body) );                
            }

            reject(err);
        });

    });
}

var hikes = {
    url: "https://api.com/hikes",
    headers: {
      'Identifier': identifier
    }
};

var availability = {
    url: "https://api.com/hikes",
    headers: {
      'Identifier': identifier
    }
};


app.get("/", function(req, res){


    let data1, data2;

    apiCall(hikes)
    .then( result => {    // Result of first call
        data1 = result;

        return apiCall(availability);
    }) 
    .then( result => {     // Result of second call
        data2 = result;

        res.render("index", {data1, data2});
    })
    .catch( err => {
        console.log("Error occured in one of the API call: ", err);
    });
});

// or with async-await

app.get("/", async function(req, res){

    try {

        let data1 = await apiCall(hikes);

        let data2 = await apiCall(availability);

        data1 = JSON.parse(data1);

        data2 = JSON.parse(data2);

        res.render("index", {hikes: data1.hikes, availability: data2.availability});
    }
    catch( err ) {
        console.log("Error occured in one of the API call: ", err);
    };
});

better index.ejs:

<% if (hikes.length>0) { %>
    <% if (availability.length>0) { %>
        <% for (var i = 0; i < hikes.length; i++) { %>

            <p><%= hikes[i].name %></p>
            <p><%= availability[i].slots %></p>
            <p><%= availability[i].available %></p>

        <% } %>
    <% } %>
<% } %>

2 Comments

Thanks alot, I will test this one out! Should I use the "request-promise" npm package when using promises? I tried to use the code with "request-promise", but I get an error message saying "ReferenceError: error is not defined".
no its new feature of javascript available in any version of node above v6
0

Concept solved with multiple request functions. result, multiple desired api values are able to be accessed by global variable. node v10.15.1:

// require node packages
const express = require("express");
const bodyParser = require("body-parser");
const request = require("request");
//set app to use express package
const app = express();
//let app use body-parser package
app.use(bodyParser.urlencoded({extended:true}));
// let app set ejs as the view engine
app.set("view engine", "ejs");
// set view path
const view = __dirname + "/app/views/";

Solution starts here:

//assign api to constant
const btcUsd = "https://apiv2.bitcoinaverage.com/indices/global/ticker/BTCUSD";
const trxBtc = "https://apiv2.bitcoinaverage.com/indices/tokens/ticker/TRXBTC";

// function containing api parser
function tickers(){
 request(btcUsd, function(error, response, body){
    // handle errors if any
    if(error){
      console.log(error);
    } else {
      // parse json
      let data = JSON.parse(body);
      // get last price
      btc = (data.last).toFixed(2);
      console.log(btc);
    }
  });

  request(trxBtc, function(error, response, body){
    // handle errors if any
    if(error){
      console.log(error);
    } else {
      // parse json
      let data = JSON.parse(body);
      // get last price
      usdConvert = (data.last) * btc;
      trx = usdConvert.toFixed(4);
      console.log(trx);
    }
  });
}
// function to trigger ticker function and set interval. (this is optional)
function getTickers(){
  tickers();
  // set interval
  setInterval(tickers, 60000);
}

//activate getTickers function
getTickers();

The desired api values btc and trx can now be used by any rendered view that assign each value to an object key:

// render view
app.get("/", function(req, res){
 res.render(views + "pages/index", {btcTicker: btc, trxTicker: trx});
});

In view:

<h1> <%= btcTicker %> </h1>
<br>
<h1> <%= trxTicker %> </h1>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.