Getting started with Github GraphQL API

GraphQL. I certainly have heard of that. Announced on every meetup here and there as “REST killer”. I have been on some presentations about it and it really makes a good impression – certainly being much more elastic and easy on resources than traditional REST APIs. And since Github recently moved its new shiny GraphQL API from closed early access, I decided to give it a try.

Here are some notes made during the process.

Warming up

If you want to call a GraphQL API from Ruby, you don’t have much choice. There is only one gem, made by Github and called graphql-client. Aside from installing it, you also need to acquire a “personal access token” for Github. This can be done on this site.

To start, you need to initialize everything:

require "graphql/client"
require "graphql/client/http"

module Github
  HTTP = GraphQL::Client::HTTP.new("https://api.github.com/graphql") do
    def headers(context)
      {
          "Authorization" => "Bearer your_token_here"
      }
    end
  end
  Schema = GraphQL::Client.load_schema(HTTP)
  Client = GraphQL::Client.new(schema: Schema, execute: HTTP)
end

That’s quite a lot of boilerplate and being GraphQL beginner I don’t quite understand all of it. But it’s necessary to get started. So, let’s write our first query. The simplest one would be to get a login of “current user”, which is the one using the token.

TestQuery = Github::Client.parse <<-'GRAPHQL'
  query {
    viewer {
      login
    }
  }
GRAPHQL

The first surprise here is that you actually need to assign the query to a constant. If you don’t the next step, where we actually execute a query, will fail with an exception raised:

expected definition to be assigned to a static constant https://git.io/vXXSE (GraphQL::Client::DynamicQueryError)

Other than that, it’s pretty straightforward – you just parse a multiline string and assign to a variable. So, let’s execute our query:

result = Github::Client.query(TestQuery) # thanks to Krzaq for spotting a typo here
p result.data.viewer.login

With that, I see my username printed to the console. So all good. Let’s move to something more complex.

A more complex example

Let’s say we want to acquire information about arbitrary Github user - his name and repositories he contributed to. We are going to start with writing a new query:

UserQuery = Github::Client.parse <<-'GRAPHQL'
  query($username: String!) {
    user(login: $username) {
      login
      contributedRepositories
    }
  }
GRAPHQL

It’s more complicated, since it actually uses variables. One variable, to be more precise. In the second line of the listing it is defined as named username, being a non-null string. Then we use it on the third line of the query. To execute it, we need to pass that username:

result = Github::Client.query(UserQuery, variables: { username: 'apotonick' })
data = result.data.user
puts data.login
p data.contributed_repositories

Note that camelCase becomes snake_case. This is one the the weird things happening in the gem. After running it, I got this:

apotonick
#< __typename="RepositoryConnection">

OK, so we’ve got the username, but this __typename="RepositoryConnection" is not very gratifying. How to reach inside and obtain repository names? If you think that just using each should be fine, know that I thought the same. But it’s not.

To proceed, we need to define a GraphQL fragment to know what RepositoryConnection is. Its structure is defined by the docs. Then we need to change our query a bit.

RepositoryConnection = Github::Client.parse <<-'GRAPHQL'
  fragment on RepositoryConnection {
    edges {
      node {
        name
        createdAt
      }
      cursor
    }
    pageInfo {
      hasNextPage
    }
  }
GRAPHQL

UserQuery = Github::Client.parse <<-'GRAPHQL'
  query($username: String!) {
    user(login: $username) {
      login
      bio
      contributedRepositories {
        ...RepositoryConnection
      }
    }
  }
GRAPHQL

And to use is, we need to instantiate the connection from the response again:

result = Github::Client.query(UserQuery, variables: { username: 'apotonick' })
data = result.data.user
puts data.login
repo_connection = RepositoryConnection.new(data.contributed_repositories)
repo_connection.edges.each do |edge|
  repo = edge.node
  p [repo.name, repo.created_at]
end

And now… Wow, that’s a lot of contributions, Nick!

Conclusions

REST killer? I don’t think so. There is a lot of potential in GraphQL but the entry threshold is quite high, comparing to plain old REST. It’s definitely better, but more complicated to use.

About the gem, it has its funkiness. Converting from camelCase to snake_case is one of them. The other is that having a response, I did not found any way to actually display raw JSON that’s underneath. Last but not least, it relies on ActiveSupport. This is, of course, good if you develop Rails application, but a killer if you use it independently. Still, better than nothing.

Related posts