tutorial // Sep 17, 2021

How to Fetch Repo Metadata with JavaScript via the Github API

How to use the JavaScript Fetch API to retrieve metadata (e.g., a star count) for a repo from the Github API.

How to Fetch Repo Metadata with JavaScript via the Github API

Getting Started

For this tutorial we're going to use the CheatCode Next.js Boilerplate as a starting point for our work. To get started, let's clone a copy of the repo for that project:

Terminal

git clone https://github.com/cheatcode/nextjs-boilerplate.git

Next, cd into the repo and install its dependencies:

Terminal

cd nextjs-boilerplate && npm install

Finally, go ahead and start up the development server:

Terminal

npm run dev

With that, we're ready to get started.

Creating a React.js Component to Render Repo Data

To begin, we're going to create a React component where we'll fetch and render our data from the Github API. For our component, we'll use the function component pattern:

/pages/index.js

import React from "react";
import StyledIndex from "./index.css";

const Index = () => {
  return (
    <StyledIndex>
      // We'll build out the core of our component here...
    </StyledIndex>
  );
};

Index.propTypes = {};

export default Index;

Because the boilerplate we're working with is based on Next.js, in order to define our component, we need to add it to the /pages directory at the root of the project. Behind the scenes, Next.js will automatically create a route in our app—in the browser—which will render the component we create (in this case, at http://localhost:5000/ as we're creating /pages/index.js).

Above, we're setting up our base component <Index /> and exporting it as the default from our file (required by Next.js). Looking at the code, we've started with the creation of a styled component: a way to add styling to a React component by dynamically generating a wrapper component—here, <StyledIndex />—that has CSS attached to it. Real quick, let's open up that /pages/index.css.js` file that's being imported here and add the styles we'll need later:

/pages/index.css.js

import styled from "styled-components";

export default styled.div`
  display: inline-block;
  border: 1px solid #eeeeee;
  padding: 40px;
  margin: 0 auto;
  border-radius: 3px;
  box-shadow: 0px 2px 2px 2px rgba(0, 0, 0, 0.02);

  > h4 {
    font-size: 16px;
    text-transform: uppercase;
    color: #aaaaaa;
  }

  > h4 .fa-star {
    color: #ffcc00;
  }

  > h1,
  > h2,
  > h3,
  > h4,
  > h5,
  > h6 {
    margin: 0;
  }

  > h2 {
    font-size: 48px;
    font-weight: bold;
    margin: 10px 0 5px;
  }

  > p {
    font-size: 20px;
    color: #888888;
    margin: 0;
  }

  > p > a {
    display: inline-block;
    font-size: 16px;
    color: #0099ff;
    margin-top: 10px;
  }

  > div {
    margin-top: 20px;
  }

  > div {
    display: flex;
    align-items: center;
  }

  > div img {
    width: 50px;
    height: 50px;
    border-radius: 50px;
    margin-right: 15px;
  }

  > div a {
    color: #aaaaaa;
  }

  > div h5 {
    margin: 0;
  }

  > div p {
    margin: 0;
  }
`;

Here, we import an object styled which contains a series of functions, each of which represent a standard HTML element. When the function is called, it expects a single JavaScript string as an argument containing CSS styles to attach to the element returned by the function.

We use the styled.div function, calling it using the short-hand method in JavaScript for calling a function where the only argument passed is a string: calling the function, but instead of parentheses, immediately follow with a pair of backticks. Between those backticks, we've passed some CSS as a string (again, backticks are just another way to author a string in JavaScript) to help us style the markup we'll add to our component next where we'll render our Github repo data.

/pages/index.js

import React, { useState, useEffect } from "react";
import StyledIndex from "./index.css";

const Index = () => {
  const [loading, setLoading] = useState(true);
  const [repoName, setRepoName] = useState("");
  const [repoDescription, setRepoDescription] = useState("");
  const [repoOwner, setRepoOwner] = useState({
    username: "",
    url: "",
    avatar: "",
  });
  const [repoURL, setRepoURL] = useState("");
  const [repoStars, setRepoStars] = useState(0);

  if (loading) {
    return <div></div>;
  }

  return (
    <StyledIndex>
      <h4>
        <i className="fas fa-star" /> {repoStars} Stars
      </h4>
      <h2>{repoName}</h2>
      <p>{repoDescription}</p>
      <p>
        <a href={repoURL}>{repoURL}</a>
      </p>
      {repoOwner && (
        <div className="owner">
          {repoOwner?.avatar && (
            <img src={repoOwner.avatar} alt={repoOwner.username} />
          )}
          <div>
            <h5>{repoOwner.username}</h5>
            <p>
              <a href={repoOwner.url}>{repoOwner.url}</a>
            </p>
          </div>
        </div>
      )}
    </StyledIndex>
  );
};

Index.propTypes = {};

export default Index;

Fleshing out our component, because we're going to be dependent on a potentially slow API request—in the general sense; under normal conditions the API is fast—we're going to put the data we want to render in our component on to state.

Here, because we're using the function component pattern (as opposed to the class-based pattern), we're going to use the useState() hook function to define, set, and retrieve our different state values.

For our needs, we have six values we want to set on state:

  1. A loading state to let us know if we've completed the API request.
  2. The repoName for the URL we request.
  3. The repoDescription for the URL we request.
  4. The repoOwner for the URL we request (made up of their username, url, and avatar).
  5. The repoURL for the URL we request (different from the API URL we'll use).
  6. The repoStars for the URL we request.

For each of the above, we make a call to useState(), passing in a default value for each and anticipating a JavaScript array in return. In those arrays, we expect the current value for our state values in the first position (0 index) and a setter function for our state values in the second position (1 index).

To make our code easier to read, to access these values we use JavaScript array destructuring. This allows us to simultaneously access and assign array values to variables. Here, for example, const [repoName, setRepoName] = useState("");, could be rewritten as:

const state = useState("");
const repoName = state[0];
const setRepoName = state[1];

Because this is more verbose—and arguably, more confusing—we use the JavaScript array destructuring pattern instead.

Below our calls to useState(), we make sure to return an empty <div></div> in the event that our loading state value is currently true (this will prevent an unnecessary flash of the HTML where we render our repo information before we've received data from the API).

Assuming loading is false, we move on to our return value, here, we have our <StyledIndex /> component we learned about above wrapped around the markup that we'll use to render our component (what we set up the styles for earlier). There's nothing too special here, just a simple card-style interface that renders out our data.

Now for the fun part. Next, we need to add the ability to fetch data from the Github API. The good news: we won't need to do any special authentication workflows as the URL we want to use https://api.github.com/repos/<user>/<repo> is a public-facing API endpoint.

Using the Fetch API to Get Repo Data from the Github API

Now, to make all of this work, we want to implement our call to the Github API. To do it, we'll use the JavaScript Fetch API for performing our HTTP requests. This API is built-in to modern web browsers, so we don't need to install anything extra.

/pages/index.js

import React, { useState, useEffect } from "react";
import StyledIndex from "./index.css";

const Index = () => {
  const [loading, setLoading] = useState(true);
  const [repoName, setRepoName] = useState("");
  const [repoDescription, setRepoDescription] = useState("");
  const [repoOwner, setRepoOwner] = useState({
    username: "",
    url: "",
    avatar: "",
  });
  const [repoURL, setRepoURL] = useState("");
  const [repoStars, setRepoStars] = useState(0);

  useEffect(() => {
    fetch(
      `https://api.github.com/repos/cheatcode/nodejs-server-boilerplate`
    ).then(async (response) => {
      // We'll handle the response from the Github API here...
    });
  }, []);

  if (loading) {
    return <div></div>;
  }

  return (
    <StyledIndex>
      ...
    </StyledIndex>
  );
};

Index.propTypes = {};

export default Index;

In between our calls to useState() and our if (loading) conditional, we've added a call to the useEffect() hook function, imported from the react dependency at the top of our file. This function allows us to run arbitrary code when our component renders, as well as when any dependencies we tell it to watch change.

Here, our goal is to run our API request as soon as our page loads and then copy the data we get from the response to that request on to our component's state values. Before we do that, inside of the callback function that we pass to useEffect() (the second argument [] can optionally contain a list of values—e.g., props—that force the callback function to fire when they change), we're setting up our call to the API using the built-in fetch() method we hinted at above.

To it, we pass the API endpoint—denoted by the api.github.com part—that we want fetch to make a request to and handle the response for. Here, https://api.github.com/repos/cheatcode/nodejs-boilerplate will return us the metadata for the CheatCode Node.js Boilerplate. To see a preview of the data we expect in return visit the URL in your browser.

In our code, to get access to that response, because we're using fetch(), we need to specify how we want to get our data in return:

/pages/index.js

import React, { useState, useEffect } from "react";
import StyledIndex from "./index.css";

const Index = () => {
  const [loading, setLoading] = useState(true);
  const [repoName, setRepoName] = useState("");
  const [repoDescription, setRepoDescription] = useState("");
  const [repoOwner, setRepoOwner] = useState({
    username: "",
    url: "",
    avatar: "",
  });
  const [repoURL, setRepoURL] = useState("");
  const [repoStars, setRepoStars] = useState(0);

  useEffect(() => {
    fetch(
      `https://api.github.com/repos/cheatcode/nodejs-server-boilerplate`
    ).then(async (response) => {
      const data = await response.json();

      // We'll copy our data over to state here...
    });
  }, []);

  if (loading) {
    return <div></div>;
  }

  return (
    <StyledIndex>
      ...
    </StyledIndex>
  );
};

Index.propTypes = {};

export default Index;

Inside of the .then() callback we've chained onto fetch() (we do this instinctively as we expect fetch() to return a JavaScript Promise), in order to get back our response body as JSON data—at this point, a JavaScript object—we call to the .json() function we anticipate on the response object passed to our .then() callback.

Because we expect this function—response.json()—to return a Promise as well, we use the async/await pattern to tell JavaScript "in this context, we expect to use the await statement." We declare that by prepending the keyword async to the outside parent scope (in this case the function passed to .then()) where our await statement will be used. This is required as JavaScript will throw a runtime error if we do not.

With this, now, in our const data variable, we should have the data back for our repo. Let's add it on to state:

/pages/index.js

import React, { useState, useEffect } from "react";
import StyledIndex from "./index.css";

const Index = () => {
  const [loading, setLoading] = useState(true);
  const [repoName, setRepoName] = useState("");
  const [repoDescription, setRepoDescription] = useState("");
  const [repoOwner, setRepoOwner] = useState({
    username: "",
    url: "",
    avatar: "",
  });
  const [repoURL, setRepoURL] = useState("");
  const [repoStars, setRepoStars] = useState(0);

  useEffect(() => {
    fetch(
      `https://api.github.com/repos/cheatcode/nodejs-server-boilerplate`
    ).then(async (response) => {
      const data = await response.json();

      if (data && data.name) {
        setRepoName(data.name);
      }

      if (data && data.description) {
        setRepoDescription(data.description);
      }

      if (data && data.owner) {
        setRepoOwner({
          username: data?.owner?.login,
          url: data?.owner?.url,
          avatar: data?.owner?.avatar_url,
        });
      }

      if (data && data.html_url) {
        setRepoURL(data.html_url);
      }

      if (data && data.stargazers_count) {
        setRepoStars(data.stargazers_count);
      }

      setLoading(false);
    });
  }, []);

  if (loading) {
    return <div></div>;
  }

  return (
    <StyledIndex>
      ...
    </StyledIndex>
  );
};

Index.propTypes = {};

export default Index;

Here, utilizing our set functions we received when we defined our state values earlier, we write a series of if statements, each ensuring that we did in fact get back data from Github and that each of the values we need are present on that response. If they are, we call to the appropriate set function—e.g., setRepoName()—passing the corresponding value from the response object that we want to render on screen.

We do this for each state variable we set, with the odd man out being the setRepoOwner which is passed an object containing three properties (as opposed to a single value passed directly).

With that, assuming everything is working well and the Github API is available, we should see our repo data rendered in the browser:

Try swapping out the URL we passed with a public repo of your own and see it render in the browser!

Wrapping up

In this tutorial, we learned how to access the public metadata for a Github repo using the Github JavaScript API. We learned how to define a React.js component in Next.js, giving us a way to fetch data from the Github API as well as render it on screen (and style it using styled components).

Finally, we learned how to perform an HTTP request using the JavaScript fetch() API as well as how to copy data from an HTTP request over to the state of our component to dynamically render it on screen.

Written By
Ryan Glover

Ryan Glover

CEO/CTO @ CheatCode