Using the GitHub GraphQL API in a serverless function - GraphQL vs REST
This isn’t a guide, this is me documenting the how and why for a proof of concept project I worked on.
So, I started playing around with the GitHub GraphQL API endpoint to do something I’ve been meaning to do for a wile now, that’s to make a serverless function that will return the GitHub stats from my GitHub profile.
Me doing this came up in conversation with my friend Paul Scanlon and he decided to do something similar but with the GitHub REST API, so we are making notes on how things are going!!
What is it you’re doing though?
What I’m doing is based off of several Leigh Halliday videos on using the GitHub GraphQL API and adding that data to a pie chart.
My current site has a similar pie chart in the about section which created at build time. This chart is created at request time from the client (the browser).
Check out the Interactive example for an idea of what’s going on.
ℹ Also Leigh has just released Next Level Next.js where you can get $10 off with my affiliate link.
Approach
The idea is to use a serverless function that will take in a GitHub usename and use that to query the GitHub GraphQL API and return data related to the username.
The API data will then be used to populate a pie chart showing the language split for that users repositories on their GitHub account.
Once that is done the serverless function will render the chart in HTML and take a picture of the chart and return that as the response to the client.
The serverless provider I’m using is Vercel, which in turn uses AWS Lambda. This means that the code I want to run is triggered by an incoming request to the code on the Vercel servers.
When I say incoming request I mean a URL, something like:
1https://serverless.vercel.app
Where the GitHub username would be passed in the request as a variable and used for the GraphQL query, so the URL in my case would look something like this:
1https://serverless.vercel.app?username=spences10
The code on Vercel then parses the request, takes out the username
variable and passes that to a GraphQL query in Axios which returns a
JSON object. The JSON is then manipulated to use in the pie chart.
The query
The GitHub GraphQL API query looks like this:
1query GITHUB_USER_DATA_QUERY($username: String!) {2 user(login: $username) {3 repositories(4 last: 1005 isFork: false6 orderBy: { field: UPDATED_AT, direction: ASC }7 privacy: PUBLIC8 ) {9 nodes {10 name11 description12 url13 updatedAt14 languages(first: 5) {15 nodes {16 color17 name18 }19 }20 }21 }22 }23}
This query will give the last 100 repos that aren’t forks and are publically viewable for that GitHub username.
Here’s what the response from the GraphQL query looks like:
1{2 "data": {3 "user": {4 "repositories": {5 "nodes": [6 {7 "name": "scottspence.com",8 "description": "My Letter Beautiful Mysterious Notebook.",9 "url": "https://github.com/spences10/scottspence.com",10 "updatedAt": "2021-01-18T17:35:19Z",11 "languages": {12 "nodes": [13 {14 "color": "#f1e05a",15 "name": "JavaScript"16 }17 ]18 }19 }20 ]21 }22 }23 }24}
Map filter reduce
The JSON data from the GraphQL call is then transformed so it will go into the data shape the pie chart is expecting.
Check out the data transform module on the repo for more detail and also Leigh’s video Map, Reduce, Filter, and Pie Charts is super helpful.
Charts
I went with Google chart library first as it had what I needed (Pie/Doughnut and Heatmap) but it’s not responsive. This isn’t a big deal as the chart is being returned as an image.
To work with the chart locally I used the live server VS Code
extension and a added the chart to an index.html
file to get an idea
of how it will look.
This really helped with getting the data from the API response to fit with what the graph was expecting changing the data transform function accordingly.
Here’s a small sample of the script
in the HTML file that indicates
the data it’s expecting:
1<script type="text/javascript">2 google.charts.load('current', { packages: ['corechart'] })3 google.charts.setOnLoadCallback(drawChart)4 function drawChart() {5 var data = google.visualization.arrayToDataTable([6 ['Languages', 'Languages Count'],7 ['JavaScript', 37],8 ['TypeScript', 13],9 ['CSS', 12],10 ['HTML', 7],11 ])12
13 var options = {14 // title: 'My Languages Split',15 colors: ['#f1e05a', '#2b7489', '#563d7c', '#e34c26'],16 chartArea: {17 left: 0,18 top: 30,19 width: '100%',20 height: '90%',21 },22 }23
24 var chart = new google.visualization.PieChart(25 document.getElementById('doughnut')26 )27 chart.draw(data, options)28 }29</script>
Take a picture
Now that the data looks ok in the pie chart I want to take a picture of it from a browser, but it’s on a server, so I need to use a headless browser like Chromium.
To do this I used Puppeteer I wanted to use Playwright but that didn’t work on Vercel so a reverted to Puppeteer like with the serverless open graph image project I made a while back now.
Latency
Loading the image does take a while, I’ve added this one below the
fold but because it’s not part of Gatsby image there will be layout
shift unless I add a default heihgt to the img
tag.
Becuse this isn’t being done at build time there is a noticeable delay in the image being served sometimes.
There may be something I can do about it with some persisted queries with OneGraph, not for this post though.
Interactive example
You can check out the latency by changing my username in the live code example here:
Big boi function
I was poking around with the function on Vercel and found that the size was at 90% 😬 the max size for AWS functions is 45mb, 99% od that is taken up by Chrome AWS lambda anf d Puppeteer core.
This didn’t really bode well as I’d only finished one part of it, the next part will be a heat map.
Colour contrast on the graph
There was some contrast issues with the text on the pie chart so I had to find a way to change the contrast of the text color.
After a bit of searching I found contrast-color-generator which offered up a colour to satisfy the W3C guidelines.
This then had to be added to the data transform to change the colour of the text.
Here’s a small snippet of how the languages are addd to an object:
1const langaugesArray = Object.entries(langObject).map(2 ([key, value]: any) => {3 return {4 id: key,5 label: key,6 value: value.count,7 color: value.color,8 textColor:9 value.color !== null10 ? generator.generate(value.color).hexStr11 : '#000000',12 }13 }14)
Compared to the REST API
Paul has done a great write up on the contrast between the two approaches.
I have outlined where my approach isn’t great with the latency from the function but there’s a lot going on with that.
From what I could glean from Paul’s approach is that the languages are only the most used language whereas the GraphQL response is all the languages used in and object.
If I used the REST API on the serverless function there would probably be the same amount of latency.
Resources
Back to Top