Retrieve Github Data Using Github GraphQL API & Apollo Client

The demo of this project is live here
Recently I have added a new stats section to my personal website where I will be sharing the metrics of my account on platforms like Youtube, Github, Unsplash, and more. The other day I wanted to get the statistics of my GitHub account like the number of stars and forks from the GitHub API, and found out that there is also a GraphQL API designed in addition to the REST API in order to get the statistics.
NOTE: There are major differences between the GraphQL & REST API and the way data is being retrieved. It's important to learn the pros and cons of both, which to use and when.
GraphQL is a query language and a powerful tool for your API. It gives you the power to retrieve the data you want and ask for exactly what you need.
There is a lot that goes into GraphQL like the type system, schema, queries, and mutations, but in this article, we will only be looking at how to query the GitHub API for user statistics.
Let’s learn together.
1. Create a new React project
For this tutorial, I’ll be using React and TypeScript. Open your terminal and create a new React project using Next.js, the Production-grade React framework as the React documentation name it.
npx create-next-app
Next.js will ask you a few questions as below:
- Type y for
Need to install the following packages
- Project name: Give your project a name that you like, in my case it’s
github-stats-graphql
- Would you like to use TypeScript: Select Yes
- Would you like to use ESLint: Select No
- Would you like to use the src directory: Select No
- Would you like to use the app directory: Select No
- What import aliases would you like configured: Just enter any
The project will be created with the necessary packages.
Open your project folder in VS Code or any other coding editor that you are using.
2. Add Tailwind CSS
We will be using Tailwind CSS for styling. Open your terminal and install Tailwind CSS:
npm install -D tailwindcss postcss autoprefixer
Then, create a Tailwind config file:
npx tailwindcss init -p
Next, replace the content section in yout tailwind.config.js
file with the following code:
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
Finally, add the Tailwind directives to your global.css
file under the styles folder:
@tailwind base;
@tailwind components;
@tailwind utilities;
In addition, I have added an extra icon package. You can install it too if you wish to. Just copy and paste the following command to your terminal:
npm install react-icons --save
3. Create a Github Access Token
In order to access the GitHub API, we need to generate a GitHub Personal Access Token and store it in our .env
file.
Go to your Github account -> settings -> Developer settings -> Personal access tokens -> Tokens (classic) -> Generate new token -> Generate new token (General Use) -> Give your token a name and select scopes. In my case, I have given it access to public_repo and read:user scopes.
Then, create a .env.local file in your project root and add your GitHub access token like this:
GITHUB_ACCESS_TOKEN = 'Your Github Access Token';
replace the text on the right side of the = sign with your GitHub access token.
4. Install GraphQL & Apollo Client
Our project is ready and now is time to query Github API using the GraphQL & Apollo client. But first, we have to install them like this
npm install @apollo/client graphql
Apollo Client is a comprehensive state management library for JavaScript that enables you to manage both local and remote data with GraphQL. Use it to fetch, cache, and modify application data, all while automatically updating your UI.
5. Create Components
Next, we need to create the necessary components for our UI before jumping in to query the API.
Create a components folder in your project root if it’s not already there.
MetaHead Component
This is a reusable component that we can use for meta info across our application.
Create a MetaHead.tsx
component and past the following code in there.
import Head from 'next/head';
export default function PageMeta({ title }: { title: string }) {
return (
<Head>
<title>{title}</title>
<meta
name="description"
content="Retrieve Data Using Github GraphQL API and Apollo Client"
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<link rel="icon" href="/favicon.ico" />
</Head>
);
}
Heading Component
Create a Heading.tsx
component and past in the following code.
export default function Heading({ title }: { title: string }) {
return (
<span className="bg-gradient-to-r from-sky-500 via-pink-400 to-yellow-500 bg-clip-text text-transparent">
{title}
</span>
);
}
Header Component
Create a Header.tsx
component under the components/common folder in your project and paste the following code there.
import Heading from '../Heading';
export default function Header() {
return (
<div>
<div className="pt-10 text-center">
<h1 className="mb-4 text-3xl font-extrabold leading-9 tracking-normal text-gray-900 dark:text-white sm:leading-none md:text-4xl lg:text-5xl">
Retrieve User Data Using{' '}
<Heading title="Github GraphQL API" /> and{' '}
<Heading title="Apollo Client" />
</h1>
</div>
<div className="mt-4 text-center text-sm tracking-normal">
<p>
The data is being retrieved for the username:{' '}
<code className="text-md rounded bg-blue-600 px-2 py-0.5 text-white dark:bg-blue-500">
@realstoman
</code>{' '}
<br />
To get your account stats, update the env file
</p>
</div>
</div>
);
}
Footer Component
Create a Footer.tsx
component under the components/common folder in your project and paste the following code there.
export default function Footer() {
const getDate = new Date().getFullYear();
return (
<footer className=" container mx-auto mb-2 mt-4 flex max-w-xl items-center justify-center rounded-lg bg-white shadow dark:bg-gray-800 sm:mt-8">
<div className="w-full py-3 text-center">
<span className="text-md text-gray-500 dark:text-gray-400 sm:text-center">
{getDate} - Developed with{' '}
<span className="text-xs">💛</span> by{' '}
<a
href="https://stoman.me/"
className="text-sky-500 underline"
target="_blank"
>
Stoman
</a>
</span>
</div>
</footer>
);
}
PageWrapper Component
Create a PageWrapper.tsx
the component in your components folder and paste the following code there.
import React from 'react';
import Footer from './common/Footer';
import Header from './common/Header';
export default function PageWrapper({
children
}: {
children: React.ReactNode;
}) {
return (
<div className="container mx-auto flex min-h-screen max-w-4xl flex-col justify-between px-8">
<Header />
{children}
<Footer />
</div>
);
}
StatsCard Component
Create a StatsCard.tsx
the component in your components folder and past the following code there.
export default function StatsCard({
stat,
statOf,
profileUrl
}: {
stat: number;
statOf: string;
profileUrl: string;
}) {
return (
<a
href={profileUrl}
className="block w-full rounded-lg border border-gray-200 bg-white p-6 shadow hover:bg-gray-100 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700"
target="_blank"
>
<h5 className="mb-2 text-2xl font-medium tracking-normal text-gray-900 dark:text-white">
{statOf}:{' '}
<span className="decoration-3 text-red-500 underline decoration-zinc-900 dark:text-yellow-400 dark:decoration-zinc-500">
{stat}
</span>
</h5>
</a>
);
}
PinnedItem Component
This is the component we will use for pinned repos of the user. Create a PinnedItem.tsx
component in your components folder and paste the following code there.
import { FiBook } from 'react-icons/fi';
export default function PinnedItem({
title,
description,
repoLink
}: {
title: string;
description: string;
repoLink: string;
}) {
return (
<a href={repoLink} target="_blank">
<div className="flex h-32 w-64 cursor-pointer flex-col justify-between rounded-lg border border-gray-200 bg-white px-4 py-2 shadow transition-all duration-300 hover:bg-gray-100 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700 sm:h-32 sm:w-72 sm:py-3">
<div className="justify-left flex items-center">
<FiBook className="mr-2 text-lg" />
<h5 className="text-md mb-2 mt-1 font-semibold text-gray-900 dark:text-white">
{title}
</h5>
</div>
<p className="mb-3 text-sm font-normal leading-5 text-gray-500 dark:text-gray-400">
{description}
</p>
</div>
</a>
);
}
Finally, open the index.tsx
file in your project pages folder and replace the code in your file with the following code.
This is a long file and contains all the logic and queries for the user statistics.
Code explanation:
- We create an async function to get the static props
- Then we create an HTTP link for Github GraphQL API
- Next, we create an auth link using the Apollo state
- Next, we create an apollo client and pass in the auth link that gets the HTTP link. In addition, we pass in a cache object. Apollo Client’s InMemoryCache stores data as a flat lookup table of objects that can reference each other. These objects correspond to the objects that are returned by your GraphQL queries. A single cached object might include fields returned by multiple queries if those queries fetch different fields of the same object. The cache is flat, but objects returned by a GraphQL query often aren’t! In fact, their nesting can be arbitrarily deep.
- Then, we query the GitHub API using GraphQL queries.
- Once we returned all the necessary data using GraphQL queries, then we pass them to the
index.tsx
component as props. - Finally, we do some math to get the total stars, total forks and etc in our index.tsx file and display the result in the return statement of our component.
import {
ApolloClient,
createHttpLink,
InMemoryCache,
gql
} from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import PageMeta from '@/components/PageMeta';
import PageWrapper from '@/components/PageWrapper';
import StatsCard from '@/components/StatsCard';
import PinnedItem from '@/components/PinnedItem';
export default function Home({
pinnedItems,
publicRepos,
totalFollowers,
totalFollowing,
totalGists
}: any) {
const githubLink = 'https://github.com/realstoman';
// Total public repos
const totalPublicRepos = publicRepos.length;
// Get public repositories stars
const getStars = publicRepos.map((repo: { stargazerCount: number }) => {
return repo.stargazerCount;
});
const totalStars = getStars.reduce(
(totalStars: number, a: number) => totalStars + a,
0
);
// Get public repositories forks
const getForks = publicRepos.map((repo: { forkCount: number }) => {
return repo.forkCount;
});
const totalForks = getForks.reduce(
(totalForks: number, a: number) => totalForks + a,
0
);
return (
<>
<PageMeta title="Github Stats GraphQL" />
<PageWrapper>
<main className="flex flex-col items-center justify-center">
<div className="mt-10 gap-2 space-y-2 sm:mt-6 sm:grid sm:grid-cols-2 sm:space-y-0">
<StatsCard
stat={totalStars}
statOf="Total Github Stars"
profileUrl={githubLink}
/>
<StatsCard
stat={totalForks}
statOf="Total Github Forks"
profileUrl={githubLink}
/>
<StatsCard
stat={totalPublicRepos}
statOf="Total Public Repos"
profileUrl={githubLink}
/>
<StatsCard
stat={totalFollowers}
statOf="Total Followers"
profileUrl={githubLink}
/>
<StatsCard
stat={totalFollowing}
statOf="Total Following"
profileUrl={githubLink}
/>
<StatsCard
stat={totalGists}
statOf="Total Gists"
profileUrl={githubLink}
/>
</div>
<div className="mt-8 w-full sm:max-w-2xl">
<div className="mb-14 flex items-start justify-between space-x-4 overflow-x-scroll bg-scroll">
<div className="ghProjects py-2">
<div className="flex items-start justify-between space-x-4">
{pinnedItems.map((item: any) => {
return (
<PinnedItem
key={item.name}
title={item.name}
description={item.description}
repoLink={item.url}
/>
);
})}
</div>
</div>
</div>
</div>
</main>
</PageWrapper>
</>
);
}
export async function getStaticProps() {
// Github GraphQL API link
const httpLink = createHttpLink({
uri: 'https://api.github.com/graphql'
});
// Create an auth link using apollo state and pass in the headers in the return object
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: `Bearer ${process.env.GITHUB_ACCESS_TOKEN}`
}
};
});
// Apollo client that gets an auth link and a memory cache constructor
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
// GraphQL queries to get the data from Github API
const { data } = await client.query({
query: gql`
{
user(login: "realstoman") {
repositories(first: 100, privacy: PUBLIC) {
edges {
node {
stargazerCount
forkCount
}
}
}
pinnedItems(first: 6) {
totalCount
edges {
node {
... on Repository {
id
name
description
url
}
}
}
}
followers {
totalCount
}
following {
totalCount
}
gists {
totalCount
}
}
}
`
});
// Destructure the data and get the user
const { user } = data;
// Get pinned items of the user
const pinnedItems = user.pinnedItems.edges.map((edge: any) => edge.node);
// Get public repos of the user
const publicRepos = user.repositories.edges.map((edge: any) => edge.node);
// Get total followers count of the user
const totalFollowers = user.followers.totalCount;
// Get total following count of the user
const totalFollowing = user.following.totalCount;
// Get total following count of the user
const totalGists = user.gists.totalCount;
return {
// Pass props to the component
props: {
pinnedItems,
publicRepos,
totalFollowers,
totalFollowing,
totalGists
}
};
}
The project source code is available here
Conclusion
This article has covered the following:
- Query Github API using GraphQL
- Create Apollo client to fetch the data from API using GraphQL
- Basic math functions like reduce
- Creating reusable components
- Adding Tailwind CSS to React