How to Build an Automated Portfolio using GitHub's GraphQL API and React
Tags: graphql, react, github, tutorial
Published: 2020-09-21 01:28:10

First of all, thanks for all the love on [last week's post](https://dev.to/imjoshellis/automate-your-portfolio-with-the-github-graphql-api-10e7)! I mentioned I'd do a followup if there as interest, so here we are for part 2.

Last week was an overview of how I'm using GitHub's API to pull data from the projects I'm working on to automate my website. If you didn't read it, don't worry, this tutorial is standalone, but you may want to go read the other post afterward.

## Getting Started

Here's what we'll be building: [live demo](https://graphql-portfolio.netlify.app). The repo is located [here on GitHub](https://github.com/imjoshellis/GraphQL-Portfolio). I also set up a [code sandbox](https://codesandbox.io/s/awesome-booth-31yxf) if you prefer.

_Note: The code sandbox will NOT work unless you add a `.env` file with your GH token in it (see below). I recommend you make a private fork in order to do so!_

As you can see, the styling will be minimal. I'll leave customization up to you to suit your style/needs.

To make this easy to follow, I'll be starting from scratch with create-react-app. TypeScript plays nicely with GraphQL, so I'll be using the TS template.

### Create React App

```bash
npx create-react-app graphql-portfolio --template typescript
```

### Install Dependencies

For this project, we'll need the following packages:

```bash
yarn add graphql graphql-tag urql dotenv
```

And these dev packages:

```bash
yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-urql
```

### What Did We Just Install?

`codegen` is a cli (command line interface) that generates hooks for us from graphql files (more on that later).

`graphql` / `graphql-tag` are required at runtime by the hooks that `codegen` will generate.

`dotenv` is used to load our GitHub authorization token into our requests to the API.

`urql` is the GraphQL client that we'll be using to communicate with GitHub's API.

### urql vs Apollo (GraphQL Clients)

I'm still figuring all this GraphQL stuff out too, so I can't comment in depth on what situations each client would be better for.

I've used both, and I actually used Apollo on my portfolio. The only reason I chose urql here is because I've been using it a lot for another project of mine, so I'm more fluent with the workflow right now.

### Codegen Setup

To get codegen working, we need to setup a config file and add a script to `package.json`.

Let's start with the config. Create a new file called `codegen.yml` in the same root directory as `package.json` with the following:

```yml
overwrite: true
schema:
  - https://api.github.com/graphql:
      headers:
        Authorization: 'Bearer ${REACT_APP_GH_TOKEN}'
documents: 'src/graphql/**/*.graphql'
generates:
  src/generated/graphql.tsx:
    plugins:
      - 'typescript'
      - 'typescript-operations'
      - 'typescript-urql'
```

As you can see, we're telling Codegen the API address, auth info, the directory where we'll put our `.graphql` files, where it should put its generated file, and which plugins we're using.

We'll set up the `REACT_APP_GH_TOKEN` in a bit.

Now that that's done, let's add the script to `package.json`

```json
// package.json
{
  "scripts": {
    /* ...Create-React-App Default Scripts, */
    "gen": "graphql-codegen -r dotenv/config --config codegen.yml"
  },
}
```

Now we'll be able to run `yarn gen` in the terminal to generate our hooks/types file.

Finally, you need to set up an access token with GitHub. Follow the steps here to get your token and come back: [Creating a personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token)

**EDIT: I just learned I misunderstood how `.env` works clientside. I'm currently researching better ways to work with private keys on public frontend apps. Suggestions are welcome. In the meantime, make sure you only allow _read_ access on the token you create!**

That token will go in a new file called `.env` in your root directory with `package.json` & `codegen.yml`:

```bash
# .env
REACT_APP_GH_TOKEN='[TOKEN HERE]'
```

We'll access that value when running `yarn gen` and also when using urql to run queries.

_Note: Be sure to add `.env` to your `.gitignore` file! You don't want that token shared publicly!_

And with that, we've done all the basic set up!

## Your First GraphQL Query

Ok, time to take a break from your terminal/IDE and head over to [the GitHub GraphQL Explorer](https://developer.github.com/v4/explorer/) and sign in with GitHub.

The starter query looks like this:

```graphql
query {
  viewer {
    login
  }
}
```

Press the 'play' button to see the response, and let's break it down, starting with the query.

## Anatomy of a GraphQL Query

The first word `query` is a GraphQL keyword. The other option here would be `mutation`. The difference is in the names: a `query` only gets access to data, while a `mutation` is able to send data that the server can work with.

If you're familiar with REST API terms, you can think of `query` as a `GET` and `mutation` as similar to `POST`/`PATCH`.

Next, we see `viewer`. In GitHub's API, this refers to the authenticated `User` -- aka you! That's the token will be for later when we implement a query in React.

Finally, inside the `viewer`, we need to specify what we want the API to give us in return. In this case, `login` returns your GitHub username.

## Anatomy of a GraphQL Response

If you pressed the play button to run the query, you'll see the response in the right area. One of the awesome things about GraphQL is the response mirrors your query as a JS/TS object (no need to deal with JSON methods).

Let's see what happens if you don't query any fields on `viewer`. The explorer won't let you run this query:

```graphql
query {
  viewer
}
```

It will automatically change the query to this:

```graphql
query {
  viewer {
    id
  }
}
```

The explorer keeps us from hitting errors, but if you ran the query without a field on `viewer`, you'd get an error response from the server because it expects you to ask for fields (otherwise, it can't give you anything in response!).

## Building Our Query

For this project, we'll be grabbing your top three pinned repositories. Test out the following in the explorer:

```graphql
query PinnedRepos {
    viewer {
      pinnedItems(first: 3) {
        edges {
          node {
            ... on Repository {
              name
              description
            }
          }
        }
      }
    }
  }
```

This is a named `query`. The only purpose of `PinnedRepos` is to give us a name to reference later. The sever doesn't care about what comes between `query` and the first `{`.

The first new line -- `pinnedItems(first: 3)` -- gets your pinned items. The part in parenthesis is a filter so the server only sends back the first 3 (since you can pin up to 6 repos).

Now, GitHub uses a complex pattern of `edges` and `node`s. We won't go into detail on how that works exactly. Basically, `edges` is all the items (in this case, 3 pinned repos), and node is an individual item.

Next, we use `... on Repository` to tell GitHub which fields we want. Right now, we're just asking for `name` and `description`. Hit the run button, and if you have pinned repos, you should see a response that mirrors the structure of our query.

To finalize the query, let's grab a few more fields:

```graphql
query PinnedRepos {
    viewer {
      pinnedItems(first: 3) {
        edges {
          node {
            ... on Repository {
              name
              description
              pushedAt
              url
              homepageUrl
            }
          }
        }
      }
    }
  }
```

`pushedAt` is what it sounds like: the time of the most recent push.

`url` returns the repo's url

`homepageUrl` returns the homepage url (if available)

## Back to React

### Set up the graphql query

Now that our query is setup, let's head back to our files and add one: `src/graphql/queries/PinnedRepos.graphql`. Go ahead and paste the query in just as it is above.

Hit save, and now that we've got our query ready, you can run `yarn gen` in the terminal to make Codegen do its thing.

If all goes well, you should see a new generated file pop up in `src/generated/graphql.tsx`. 

### Set up the urql client

Now let's get urql up and running. Open `App.tsx` so we can initialize an urql client and wrap our app in a provider. Note: We haven't created the `<PinnedRepos />` component yet, but we'll add it right after this.

```tsx
import React from 'react'
import { createClient, Provider } from 'urql'
import './App.css'
import PinnedRepos from './components/PinnedRepos'

const client = createClient({
  url: 'https://api.github.com/graphql',
  fetchOptions: {
    headers: { authorization: `Bearer ${process.env.REACT_APP_GH_TOKEN}` }
  }
})

const App = () => (
  <Provider value={client}>
    <div className='App'>
      <h1>My Automated Portfolio</h1>
      <PinnedRepos />
    </div>
  </Provider>
)

export default App
```

We're not doing anything special in `createClient` other than adding our auth token. Every request you make will use the token so GitHub's server knows it's you asking for the data.

Create a simple `<PinnedRepos />` component in `scr/components/PinnedRepos.tsx` to make sure everything's working:

```tsx
import React from 'react'
import { usePinnedReposQuery } from '../generated/graphql'

export const PinnedRepos: React.FC = () => {
  const [{ data }] = usePinnedReposQuery()
  console.log(data)
  return <>{data ? <p>Loaded</p> : <p>Loading...</p>}</>
}

export default PinnedRepos
```

If you load up React on a local server by running `yarn start`, you should see 'Loading...' for a split second and then 'Loaded'. In your console, you'll see the data object, which should match the test query we did in the explorer:

```js
{
  viewer: {
    pinnedItems: {
      edges: Array(3)
    }
  }
}
```

So then to display the data, we just need to map over the edges. To make things simple, I'm using inline JSX styles here. For a real website, I highly recommend using CSS or some kind of style library!

```tsx
import React from 'react'
import { usePinnedReposQuery } from '../generated/graphql'

export const PinnedRepos: React.FC = () => {
  const [{ data }] = usePinnedReposQuery()
  return (
    <>
      {data?.viewer.pinnedItems.edges ? (
        <div
          style={{
            display: 'flex',
            flexDirection: 'row',
            justifyContent: 'center',
            textAlign: 'left'
          }}
        >
          {data.viewer.pinnedItems.edges.map((node, index) => {
            if (node && node.node) {
              const { name, description, url, homepageUrl, pushedAt } = {
                name: '',
                description: '',
                url: '',
                homepageUrl: '',
                pushedAt: '',
                ...node.node
              }
              return (
                <div
                  key={index}
                  style={{ marginLeft: '1rem', maxWidth: '24rem' }}
                >
                  <h2>{name}</h2>
                  {pushedAt ? <p>updated: {pushedAt}</p> : null}
                  <h4 style={{ marginBottom: 0 }}>Description</h4>
                  <p style={{ marginTop: 0 }}>
                    {description ? description : 'no description'}
                  </p>
                  <a href={url}>View on GitHub</a>
                  {homepageUrl ? (
                    <a href={homepageUrl} style={{ marginLeft: '1rem' }}>
                      View website
                    </a>
                  ) : null}
                </div>
              )
            } else {
              return null
            }
          })}
        </div>
      ) : (
        <p>Loading...</p>
      )}
    </>
  )
}

export default PinnedRepos
```

And that's it! You now have a minimal React app that's using data from your GitHub pinned repos. What you do with that data (or other data you might query) is totally up to you, so I'll leave you with this. [Check out last week's post](https://dev.to/imjoshellis/automate-your-portfolio-with-the-github-graphql-api-10e7) to see some of the other queries I'm using on my portfolio. 

## Resources

+ [This Post's GitHub Repo](https://github.com/imjoshellis/GraphQL-Portfolio)
+ [This Post on Code Sandbox](https://codesandbox.io/s/awesome-booth-31yxf)
+ [This Post's Demo on Netlify](https://graphql-portfolio.netlify.app)
+ [Automate Your Portfolio with the GitHub GraphQL API](https://dev.to/imjoshellis/automate-your-portfolio-with-the-github-graphql-api-10e7)
+ [urql docs](https://formidable.com/open-source/urql/docs/)
+ [GitHub GraphQL API docs](docs.github.com/en/graphql/)
+ [Test queries in your browser with the GitHub GraphQL explorer](https://developer.github.com/v4/explorer/)
+ [My portfolio on GitHub](https://github.com/imjoshellis/imjoshellis.com)
+ [My portfolio website](https://imjoshellis.com)

## Let's Talk

If you have any questions, leave a comment, and I'll do my best to answer it! Also, I'm still learning GraphQL, so please let me know if I included any misinformation. 

Thanks for reading!