First of all, thanks for all the love on last week's post! 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. The repo is located here on GitHub. I also set up a code sandbox 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
npx create-react-app graphql-portfolio --template typescript
Install Dependencies
For this project, we'll need the following packages:
yarn add graphql graphql-tag urql dotenv
And these dev packages:
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:
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
// 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
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
:
# .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 and sign in with GitHub.
The starter query looks like this:
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:
query {
viewer
}
It will automatically change the query to this:
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:
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:
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.
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:
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:
{
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!
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 to see some of the other queries I'm using on my portfolio.
Resources
- This Post's GitHub Repo
- This Post on Code Sandbox
- This Post's Demo on Netlify
- Automate Your Portfolio with the GitHub GraphQL API
- urql docs
- GitHub GraphQL API docs
- Test queries in your browser with the GitHub GraphQL explorer
- My portfolio on GitHub
- My portfolio website
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!
Top comments (1)
There is plenty more to talk about when it comes to GraphQL. What did I leave out? What do you want to learn about?
Maybe I can write about creating a GraphQL server next?