Back to blogs
Blog
Writing in public
Longer posts — published here first, then cross-posted to Dev.to with a canonical URL back to this site.
Blog
Longer posts — published here first, then cross-posted to Dev.to with a canonical URL back to this site.
Back to blogs
Back to blogs
Postgraphql, github, nextjs, reactquer
working with Github's Graphql api After working with the github rest api and experiencing...
Date published

Modify your nextauth client and add a callbacks section that will get the access token from the...

Why You Might Still Pick Next.js Over TanStack Start From a TanStack Start...

You can select the scopes you want to request from GitHub/your oauthprovider in the login page, and...
After working with the github rest api and experiencing some of it's limitations it's time to pick things up a bit and dive into their Graphql api
as Usual first things first we set up a testing environment and github provides their own explorer which will setup the authentication headers for you under the hood , but if you prefer to use another gql explorer , just set it up like below Replace ghp_LBgN6pCeWsHcxeoJpR4ZTwUj with your personal access token

I have another article i made about react-query tips and tricks which might help explain that part . because i want to focus on the actual GraphQl queries in this one i'll mostly highlight the queries i used in the project and not go into details about the react part
let go
before we start we'll define a graphql Fragment that defines a User, this will come in handy because we'll be using it when querying the viewer ,user, follower , and following for uniformity so that if we need to add or remove a field we just do it once in the fragment and all the items defined by it change along with it.
1import gql from "graphql-tag";23/// user fragments4export const OneUserFrag = gql`5 fragment OneUser on User {6 id7 name8 login9 email10 bio11 avatarUrl12 company13 twitterUsername14 createdAt15 isFollowingViewer16 viewerIsFollowing17 isViewer18 location19 url20 followers(first: 1) {21 totalCount22 nodes {23 id24 }25 }26 following(first: 1) {27 totalCount28 nodes {29 id30 }31 }3233 repositories(first:1){34 totalCount35 nodes{36 id37 }38 }39 }40`;
>If you remember the REST api was lacking a few fields in the equivalent query isFollowingViewer , viewerIsFollowing , isViewer which will help us avoid having to run other sub queries to check the follow status on every follower/following item
This query returns the currently logged in User , and I am using the personal access token to authenticate users since it's the simplest to implement.
1//get currently logged in user2export const GETVIEWER = gql`3 query getViewer {4 viewer {5 ...OneUser6 }7 }8 ${OneUserFrag}9`;
you'll notice that We're only fetching one follower , following , repository
1 following(first: 1) {2 totalCount3 nodes {4 id5 }6 }
and that's because we're only interested in the totalCount field at this point in order to diply it like this with all the counts

This is also because those three field require pagination and take the first ,last , after and before parameters
last and first is number of nodes and from which portion you want them from start of end and before and after are cursors , the api doe's generate cursors for us and can be accessed inside the page info field that's available in every paginated field
1export const getUserWithFollowers = gql`2 query getUserFollowers($login: String!, $first: Int, $after: String) {3 user(login: $login) {4 followers(first: $first, after: $after) {5 pageInfo {6 endCursor7 hasNextPage8 hasPreviousPage9 startCursor10 }11 totalCount12 edges {13 node {14 ...OneUser15 }16 }17 }18 }19 }20 ${OneUserFrag}21`;
in this example to fetch the next values , we'll pass in the endCursor into the after field on the next query
also note the GraphQl types
1$login: String!, $first: Int, $after: String
where String! is required and cannot be null , but String can be null , which is why the in initial query you can pass in after as null
This is also an example of graphql variables , where
1 query getUserFollowers($login: String!, $first: Int, $after: String) {}
wiltake in the variables login ,first and after and pass them into the query
1user(login: $login) {2followers(first: $first, after: $after) {
and with queries with no variables you'll just write
1 query getViewer {2 viewer {}3 }
i've found it easier to avoid stuffing multiple paginated fields into one query and just break them of into smaller queries to be run by their own component which instead of a giant query to be rendered in one component
for example , once the viewer has been fetched the smaller components nested in the main component will have their own queries one for repositories another for followers and following they will be optionally rendered in a tab like way where by default it'll load the repository tab and the others will be shown if the user explicitly clicks on them which is when they'll run their sub query
similar to the viewer query but this will take one login (username) as a parameter and return the OneUserFragMent , useful when you want to navigate to another User obtained either from the follower list or Search results
1export const GETONEUSER = gql`2 query getUser($login: String!) {3 user(login: $login) {4 ...OneUser5 }6 }7 ${OneUserFrag}
we'll also want the ability to search for random github user's by their username or password
1export const USERSEARCH = gql `23query userSearch($query:String!,$first:Int,$type:SearchType!){4 search(query:$query,first:$first,type:$type){5 repositoryCount6 discussionCount7 userCount8 codeCount9 issueCount10 wikiCount1112 edges{13 node{14 ... on User {15 login16 name17 email18 avatarUrl19 url20 }21 }22 }23 }24}2526`
the syntax below is better explained here but i short it lets us access items of a specific fragment since this query can return fragments of different types User , Repository , Code, Issue....
1 ... on User {
which allows you to write highly customizable queries like this
1query userSearch($query:String!,$first:Int,$type:SearchType!){2 search(query:$query,first:$first,type:$type){3 repositoryCount4 discussionCount5 userCount6 codeCount7 issueCount8 wikiCount910 edges{11 node{12 ... on User {13 login14 name15 email16 avatarUrl17 url18 }19 ... on Repository{20 name21 url22 }23 ... on Issue{24 id25 body26 }27 }28 }29 }30}
the follower query is basically this query
1//get currently logged in user2export const GETVIEWER = gql`3 query getViewer {4 viewer {5 ...OneUser6 }7 }8 ${OneUserFrag}9`;
1export const GETONEUSER = gql`2 query getUser($login: String!) {3 user(login: $login) {4 ...OneUser5 }6 }7 ${OneUserFrag}
but with the OneUser fragment being requested inside the the follower field
1export const getUserWithFollowers = gql`2 query getUserFollowers($login: String!, $first: Int, $after: String) {3 user(login: $login) {4 followers(first: $first, after: $after) {5 pageInfo {6 endCursor7 hasNextPage8 hasPreviousPage9 startCursor10 }11 totalCount12 edges {13 node {14 ...OneUser15 }16 }17 }18 }19 }20 ${OneUserFrag}21`;
1export const getUserWithFollowing = gql`2 query getUserFollowing($login: String!, $first: Int, $after: String) {3 user(login: $login) {4 following(first: $first, after: $after) {5 pageInfo {6 endCursor7 hasNextPage8 hasPreviousPage9 startCursor10 }11 totalCount12 edges {13 node {14 ...OneUser15 }16 }17 }18 }19 }20 ${OneUserFrag}21`;
this field has a lot on it and it'll be all about what you want to display in your app in my case i wanted to display something like this

I used ben awad's profile because his repositories actually have stars ,forks and multiple languages which is the brief info i want to see at a glance before i decide to click on it and see more
to achieve this i used this query
1export const REPOS = gql`2 query getRepos($name: String!, $first: Int, $after: String) {3 user(login: $name) {4 login5 repositories(6 after: $after7 first: $first8 orderBy: { field: PUSHED_AT, direction: DESC }9 ) {10 edges {11 node {12 id,13 name,14 description,15 pushedAt,16 diskUsage,17 url,18 visibility,19 forkCount,20 stargazers(first: $first) {21 totalCount22 },23 refs(24 refPrefix: "refs/heads/"25 orderBy: { direction: DESC, field: TAG_COMMIT_DATE }26 first: 227 ) {28 edges {29 node {30 name31 id32 target {33 ... on Commit {34 history(first: 1) {35 edges {36 node {37 committedDate38 author {39 name40 }41 message42 }43 }44 }45 }46 }47 }48 }49 }5051 languages(first: $first) {52 edges {53 node {54 id55 color56 name57 }58 }59 }60 }61 cursor62 }63 totalCount64 pageInfo {65 startCursor66 endCursor67 hasNextPage68 hasPreviousPage69 }70 }71 }72 }73 `;74
which is one moderately chonky query but returns everything i need in one query,
1 refs(2 refPrefix: "refs/heads/"3 orderBy: { direction: DESC, field: TAG_COMMIT_DATE }4 first: 25 ) {6 edges {7 node {8 name9 id10 target {11 ... on Commit {12 history(first: 1) {13 edges {14 node {15 committedDate16 author {17 name18 }19 message20 }21 }22 }23 }24 }25 }26 }27 }
in this bit am requesting for the 2 most recent commits and the branch on which it was made , the fact that this is possible in one query blows my mind which is another reason i really like graphql
but to top it all off am planning to implement a bigger query which i abandoned after realising it would be a pagination nightmare and would be better off being split up into smaller queries and each query being assigned it's child component which an be optionally rendered on user request but here it is anyway
1const FULLREPO = gql`2# github graphql query to get more details3 query getRepoDetails(4 $repoowner: String!,5 $reponame: String!,6 $first: Int,7 $after: String,8 ) {9 repository(owner: $repoowner, name: $reponame) {10 nameWithOwner,1112 # get the repo collaborators1314 collaborators(first: $first, after: $after) {15 edges {16 node {17 avatarUrl,18 email,19 name,20 bio,21 company22 },23 },24 pageInfo {25 endCursor,26 hasNextPage,27 hasPreviousPage,28 startCursor29 },30 totalCount31 }32 # end of collaborators3334 # gets the repo vulnerabilities3536 vulnerabilityAlerts(first: $first, after: $after) {37 edges {38 node {39 createdAt,40 securityAdvisory {41 classification,42 description,43 vulnerabilities(first: $first, after: $after) {44 edges {45 node {46 severity,47 package {48 name,49 ecosystem50 }51 }52 },53 pageInfo {54 endCursor55 hasNextPage56 hasPreviousPage57 startCursor58 },59 totalCount60 }61 }62 }63 }64 },65 # end of vulnerabilities block6667 #refs: get branches and all the recent commits to it6869 refs(70 refPrefix: "refs/heads/"71 orderBy: { direction: DESC, field: TAG_COMMIT_DATE }72 first: $first73 after: $after74 ) {75 edges {76 node {77 name78 id79 target {80 ... on Commit {81 history(first: $first, after: $after) {82 edges {83 node {84 committedDate,85 author {86 name,87 email88 },89 message,90 url,91 pushedDate,92 authoredDate,93 committedDate94 }95 }96 }97 }98 }99 }100 },101 pageInfo {102 endCursor,103 hasNextPage,104 hasPreviousPage,105 startCursor,106 },107 totalCount108 nodes {109 associatedPullRequests(first: $first, after: $after) {110 pageInfo {111 endCursor,112 hasNextPage,113 hasPreviousPage,114 startCursor,115 },116 totalCount117 }118 }119 }120121 # end of refs block122 # languages123 languages(first: $first, after: $after) {124 edges {125 node {126 id,127 color,128 name129 }130 },131 pageInfo {132 endCursor,133 hasNextPage,134 hasPreviousPage,135 startCursor136 },137 totalCount138 }139140 # end of languages block141 forkCount142 #fork block143 forks(first: $first, after: $after) {144 edges {145 node {146 createdAt,147 nameWithOwner,148 description,149 url,150 owner {151 login,152 url153 },154 parent {155 url,156 owner {157 login,158 url159 }160 }161 }162 }163 pageInfo {164 endCursor,165 hasNextPage,166 hasPreviousPage,167 startCursor,168 },169 totalCount170 }171 # end of fork block172173 # star block174 stargazers(first: $first, after: $after) {175 edges {176 node {177 login,178 url179 }180 }181 pageInfo {182 endCursor,183 hasNextPage,184 hasPreviousPage,185 startCursor186 }187 totalCount188 }189 #end of star block190 }191 }192`;193
the query works fine , but only if you don't paginate because then you'll have to add more after variable for every paginated field and also handle the react-query / your gql client of choice
btw , check out example usage of react-query with graphql
{% embed https://gist.github.com/tigawanna/32acc9552e02634609184dc14f2076a3 %}