Showing statistics of popular JavaScript frameworks from GitHub API I am currently working on a simple app to check the stats of popular javascript frameworks. For this I am using the Github API to get certain data sets and have come close to finishing my project.

const frameworks = [
  {
    name: "angular",
    logo: "img/angular.png",
    url: "https://api.github.com/repos/angular/angular.js"
  }
  /* 
  {
    name: "ember",
    logo: "img/ember.png",
    url: "https://api.github.com/repos/emberjs/ember.js"
  },
  {
    name: "react",
    logo: "img/react.png",
    url: "https://api.github.com/repos/facebook/react"
  },
  {
    name: "vue",
    logo: "img/vue.png",
    url: "https://api.github.com/repos/vuejs/vue"
  }
  */
];

const frameworkData = url =&gt; {
  return fetch(url)
    .then(resp =&gt; {
      return resp.json();
    })
    .then(data =&gt; {
      return {
        data: {
          name: data.name,
          forks: data.forks_count,
          stars: data.stargazers_count,
          issues: data.open_issues_count
        }
      };
    });
};

const setup = () =&gt; {
  let now = new Date();
  const row = document.getElementById("row");

  frameworks.forEach(framework =&gt; {
    const promises = [];
    promises.push(frameworkData(framework.url));

    Promise.all(promises).then(data =&gt; {
      const obj = data[0].data;
      console.log(obj);
      const card = `
      &lt;div class="col-lg-3"&gt;
        &lt;div class="card" style="width: 18rem;"&gt;
          &lt;img src="${
            framework.logo
          }" alt="Card image cap" height="180" width="200"&gt;
          &lt;div class="card-body"&gt;
            &lt;h5 class="card-title"&gt;${framework.name}&lt;/h5&gt;
          &lt;/div&gt;
          &lt;ul class="list-group list-group-flush"&gt;
            &lt;li class="list-group-item"&gt;Watchers: ${obj.forks}&lt;/li&gt;
            &lt;li class="list-group-item"&gt;Stars: ${obj.stars}&lt;/li&gt;
            &lt;li class="list-group-item"&gt;Commits: ${obj.issues}&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/div&gt;
      &lt;/div&gt;`;
      row.innerHTML += card;
    });
  });
};

setup();


Being this is one of my first javascript 'apps' I feel it was done to the best of my ability. However, I feel there is a code smell in here that I have been trying to figure out how to fix but have not.

As you may be able to see in the frameworks for each loop, I am settings this variable const obj = data[0].data;. To me this is strange and I could not seem to find away around it. This is done because the single object for that API call is being returned in an array and I was not sure how to get that data other than calling the first index on the array since it will always be 0. Is there a better way to do this? How was my first javascript app?
Your questions


  How was my first javascript app?


Overall the code seems somewhat fine for a first attempt (barring the flaw mentioned in the first section below). It isn't difficult to read though it does have a few extra levels of nesting which could be cleaned up with abstraction code functions out to named functions. 


  Is there a better way to do this?


As was suggested by others, there isn't really a point to returning the data nested under a key "data". Refer also to the other suggestions below for simplifying the code.

Other Feedback/Suggestions

Likely implementation flaw


frameworks.forEach(framework =&gt; {
  const promises = [];
  promises.push(frameworkData(framework.url));
  Promise.all(promises)



This code will call Promises.all() passing an array with a single promise, which seems pointless. While there is no rule that says there has to be two or more promises it basically defeats the purpose. 

Presumably you meant to push the promises into an array and pass the array with all promises to the call to Promise.all(). 

Creating array of promises

One could use Array.prototype.map() to construct the array. 

const promises = frameworks.map(framework =&gt; frameworkData(framework.url));


utilizing data

If you take the advice above, the first argument in a callback to .then() chained to Promise.all() should be an array of the data objects returned by the last promise callback within each call to frameworkData(), and there won’t be any reference to ‘frameworkso you might either need to pass that object along through the call toframeworkData` or look it up by index. 

Template inline

The template literal used to define card could be moved out to a template tag with slots  or a &lt;script type="text/template”&gt; that can be referenced by id attribute in the code. For more of an explanation of this, refer to this post, as well as an example application of it in this post. The requirements for this may be a bit complex for a beginner but the motivation is to keep the markup (i.e. HTML) separate from the business logic (i.e. JavaScript). 

A script tag like this could be added to the body within the HTML:

&lt;script type=“text/template” id=“card-template”&gt;
    &lt;div class="col-lg-3"&gt;
        &lt;div class="card" style="width: 18rem;"&gt;
            &lt;img src="${logo}" alt="Card image cap" height="180" width="200"&gt;
            &lt;div class="card-body"&gt;
                &lt;h5 class="card-title"&gt;${name}&lt;/h5&gt;
            &lt;/div&gt;
            &lt;ul class="list-group list-group-flush"&gt;
                &lt;li class="list-group-item"&gt;Watchers: ${forks}&lt;/li&gt;
                &lt;li class="list-group-item"&gt;Stars: ${stars}&lt;/li&gt;
                &lt;li class="list-group-item"&gt;Commits: ${issues}&lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/script&gt;


Then that could be referenced via the JavaScript:

const cardTemplate = document.getElementById('card-template').innerHTML;


You would then need interpolate the string, and unfortunately that isn't in a template literal anymore. However, an approach like outlined in this post should suffice. 

The following function added in the JavaScript like this:

String.prototype.interpolate = function(params) {
  const names = Object.keys(params);
  const vals = Object.values(params);
  return new Function(...names, `return \`${this}\`;`)(...vals);
}


Would allow for a line like this:

row.innerHTML += cardTemplate.interpolate(data);


Simplifying single line returns in arrow statements

Arrow functions that simply have one line in the block, like:


resp =&gt; {
  return resp.json();
}



Can be simplified to a single line without the block and return:

resp =&gt; resp.json()


Unused variable

The following variable doesn't appear to be utilized:


let now = new Date();



It can be removed unless you plan to utilize it in the future. If the value never changes, it would be wise to declare it with const.

Putting it all together

See simplified code below.



String.prototype.interpolate = function(params) {
  const names = Object.keys(params);
  const vals = Object.values(params);
  return new Function(...names, `return \`${this}\`;`)(...vals);
}
const frameworks = [{
    name: "angular",
    logo: "img/angular.png",
    url: "https://api.github.com/repos/angular/angular.js"
  },
  {
    name: "ember",
    logo: "img/ember.png",
    url: "https://api.github.com/repos/emberjs/ember.js"
  },
  {
    name: "react",
    logo: "img/react.png",
    url: "https://api.github.com/repos/facebook/react"
  },
  {
    name: "vue",
    logo: "img/vue.png",
    url: "https://api.github.com/repos/vuejs/vue"
  }
];

const frameworkData = framework =&gt; {
  return fetch(framework.url)
    .then(resp =&gt; resp.json())
    .then(data =&gt; {
      return {
          logo: framework.logo,
          name: data.name,
          forks: data.forks_count,
          stars: data.stargazers_count,
          issues: data.open_issues_count
      };
    });
};

const setup = () =&gt; {
  const row = document.getElementById("row");
  const cardTemplate = document.getElementById('card-template').innerHTML;
  const promises = frameworks.map(framework =&gt; frameworkData(framework));
  Promise.all(promises).then(data =&gt; {
    row.innerHTML = data.map(obj =&gt; cardTemplate.interpolate(obj));
  });
};

document.addEventListener('DOMContentLoaded', setup);
&lt;div id="row"&gt;&lt;/div&gt;
&lt;script type="text/template" id="card-template"&gt;
  &lt;div class="col-lg-3"&gt;
    &lt;div class="card" style="width: 18rem;"&gt;
      &lt;img src="${logo}" alt="Card image cap" height="180" width="200"&gt;
      &lt;div class="card-body"&gt;
        &lt;h5 class="card-title"&gt;${name}&lt;/h5&gt;
      &lt;/div&gt;
      &lt;ul class="list-group list-group-flush"&gt;
        &lt;li class="list-group-item"&gt;Watchers: ${forks}&lt;/li&gt;
        &lt;li class="list-group-item"&gt;Stars: ${stars}&lt;/li&gt;
        &lt;li class="list-group-item"&gt;Commits: ${issues}&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/script&gt;