Published on

Playing Around With Githubs GraphQL API

Authors

Tips on Building Your GitHub GraphQL Query

I wanted to get some data from Github and found that the recommended way to do this nowadays is using their v4 API which is a GraphQL endpoint. There is a setup process to get an app token but to play around with the API you do not need to do this.

Instead head to the Github V4 explorer here and click Sign in with GitHub. Once you have granted access you are returned to the GraphQL explorer.

A number of things I picked up which make exploring GraphQL APIs super easy are:

  • Getting Query Completion/Suggestions: ctrl+space
    • This is the easiest way to see what is available in the API
    • ctrl+space in the parameter section for a property to see what parameters it takes.
    • ctrl+space in the angle brackets section to see the values it can return
  • Viewing the Docs for a Query Element: ctrl+click a value to see the docs for it
  • Error warnings: The explorer tells you about errors immediately. For example if you need to provide parameters for a query the explorer tells you. If you have to provide fields for an element again the query explorer tells you with an error.
  • Prettify: The Prettify button helps plenty in formatting your query nicely
    • Beware this will remove your comments
  • Not everything can be worked out: Google anything else you are not sure about
    • For example getting the number of commits for a repo is not trivial via the api you do this using (note num_commits: is an alias as described below):
num_commits: ref(qualifiedName: "master") {
    target {
      ... on Commit {
        history {
          totalCount
        }
      }
    }
  }
  • Use the network panel: If you are not sure how this query will translate to a cURL request or whatever webservice client you are using then do the following:
    • Put you query in the explorer
    • Open the network panel in your browser: Cmd/Ctrl+Shift+I
    • Go to the network tab
    • Click the run button
    • Look at the response body and headers
    • Note: you will need the following also when doing this via a client and not the explorer:
      • You need to POST to the https://api.github.com/graphql endpoint
      • You need to have an Authorization: bearer yourAppToken header (google how to setup a Github app to create your token)

Useful GraphQL Features

A number of GraphQL features helped me build up my queries more easily and also make them more readable and maintainable:

  • Templates AKA Fragments: You use this if you have a huge query you want to repeat but with different parameters:
    • This avoids repetition and also makes your code more maintainable as you only need to update common sections in one place
  • Arguments: Some but not all queries can and sometimes require parameters. These are like arguments to a method in a JavaScript function that you are calling.
  • Aliases: This allows you to name queries to whatever you want. This makes your query more readable and is actually required if you want to hit the same field again with different parameters
    • For example for a repo if I wanted to get total issues, open issues and closed issues I need to hit the same query with different parameters meaning I have to alias this. This is done as below:
total_issues: issues(states: [OPEN, CLOSED]) {
    totalCount
  }
  open_issues: issues(states: [OPEN]) {
    totalCount
  }
  closed_issues: issues(states: [CLOSED]) {
    totalCount
  }
  • GraphQL File Extension: If you want to save your query to a file the recommended extension (which Facebook themselves use) is .graphql as can be seen here.

  • Comments: You comment using a # at the beginning of a line:

# this is a comment

Example Query

I built a query to get some of the basic stats for one or more different repos. In the below example I am comparing the React and Angular frameworks.

fragment code_repo_details on Repository {
  num_commits: ref(qualifiedName: "master") {
    target {
      ... on Commit {
        history {
          totalCount
        }
      }
    }
  }
  createdAt
  forkCount
  total_issues: issues(states: [OPEN, CLOSED]) {
    totalCount
  }
  open_issues: issues(states: [OPEN]) {
    totalCount
  }
  closed_issues: issues(states: [CLOSED]) {
    totalCount
  }
  stars: stargazers {
    totalCount
  }
  milestones {
    totalCount
  }
  total_prs: pullRequests(states: [OPEN, CLOSED, MERGED]) {
    totalCount
  }
  open_prs: pullRequests(states: [OPEN]) {
    totalCount
  }
  closed_prs: pullRequests(states: [CLOSED]) {
    totalCount
  }
  merged_prs: pullRequests(states: [MERGED]) {
    totalCount
  }
  updatedAt
  watchers {
    totalCount
  }
}

{
  # this repo's URL is: https://github.com/facebook/react
  # owner = main user in repo which can be seen in a repo's URL as follows
  # https://github.com/thisIsWhereTheOwnerIs/thisIsTheRepoName
  react: repository(name: "react", owner: "facebook") {
    ...code_repo_details
  }
  # this repo's URL is: https://github.com/angular/angular
  angular: repository(name: "angular", owner: "angular") {
    ...code_repo_details
  }
}

As of the time of writing this article this outputs the following JSON:

{
  "data": {
    "react": {
      "num_commits": {
        "target": {
          "history": {
            "totalCount": 10559
          }
        }
      },
      "createdAt": "2013-05-24T16:15:54Z",
      "forkCount": 21405,
      "total_issues": {
        "totalCount": 6786
      },
      "open_issues": {
        "totalCount": 397
      },
      "closed_issues": {
        "totalCount": 6389
      },
      "stars": {
        "totalCount": 118003
      },
      "milestones": {
        "totalCount": 39
      },
      "total_prs": {
        "totalCount": 7534
      },
      "open_prs": {
        "totalCount": 128
      },
      "closed_prs": {
        "totalCount": 2159
      },
      "merged_prs": {
        "totalCount": 5247
      },
      "updatedAt": "2018-12-20T13:48:34Z",
      "watchers": {
        "totalCount": 6592
      }
    },
    "angular": {
      "num_commits": {
        "target": {
          "history": {
            "totalCount": 12368
          }
        }
      },
      "createdAt": "2014-09-18T16:12:01Z",
      "forkCount": 11231,
      "total_issues": {
        "totalCount": 16806
      },
      "open_issues": {
        "totalCount": 2226
      },
      "closed_issues": {
        "totalCount": 14580
      },
      "stars": {
        "totalCount": 43678
      },
      "milestones": {
        "totalCount": 61
      },
      "total_prs": {
        "totalCount": 10887
      },
      "open_prs": {
        "totalCount": 394
      },
      "closed_prs": {
        "totalCount": 7603
      },
      "merged_prs": {
        "totalCount": 2890
      },
      "updatedAt": "2018-12-20T13:43:46Z",
      "watchers": {
        "totalCount": 3309
      }
    }
  }
}