Genqlient

At work, we do something useful with GraphQL & TypeScript — we generate types, so that a client can make typed calls to the API.

It’s a good development experience, and one of the strengths of GraphQL. I’ve been on the lookout for a similar experience in Go when working with a GraphQL API.

I recently came across Khan Academy’s genqlient, so I wanted to evaluate it.

Example

We’ll be using the Github GraphQL API.

In a simple Go project, I’ve written my GraphQL query in a file called repository.graphql.

query GetRepository($name: String!, $owner: String!) {
repository(name: $name, owner: $owner) {
name
description
url
stargazerCount
}
}

Here’s a few configuration things I’ve done:

  • We have a configuration file, genqlient.yaml, where we’re defining configuration options for Genqlient.
  • Download the GraphQL schema file. You can download it here. The filename needs to match the schema option in our Genqlient configuration file. I’ll call it github.graphql.

Here is the layout of my project:

├── generated
│ └── graphql.go
├── genqlient.yaml
├── github.graphql
├── go.mod
├── go.sum
├── graphql
│ └── repository.graphql
└── main.go

Let’s review the configuration file so far.

# the schema for our API
schema: github.graphql
# Our GraphQL files
operations:
- "graphql/*.graphql"
# where the generated code will live
generated: generated/graphql.go
package: generated

Now I’ll use that query:

main.go
1
package main
2
3
import (
4
"context"
5
"fmt"
6
"net/http"
7
8
"github.com/Khan/genqlient/graphql"
9
"github.com/alexhwoods/evaluate-genqlient/generated"
10
)
11
12
//go:generate go run github.com/Khan/genqlient genqlient.yaml
13
14
const GithubGraphqlAPI = "https://api.github.com/graphql"
15
16
17
func main() {
18
key := "<personal-access-token>"
19
20
ctx := context.Background()
21
22
// I'm glossing over authentication - see full example
23
client := graphql.NewClient(GithubGraphqlAPI, &httpClient)
24
resp, err := generated.GetRepository(ctx, client, "kubernetes", "kubernetes")
25
26
if err != nil {
27
fmt.Printf("error: %s", err)
28
}
29
30
useRepository(resp.Repository)
31
}
32
33
func useRepository(repository generated.GetRepositoryRepository) {
34
fmt.Printf("repository.Name: %v\n", repository.Name)
35
fmt.Printf("repository.Description: %v\n", repository.Description)
36
fmt.Printf("repository.StargazerCount: %v\n", repository.StargazerCount)
37
fmt.Printf("repository.Url: %v\n", repository.Url)
38
}

We have two useful things from the generated code:

  • A generated.GetRepository function, with typed inputs. Here’s what I see when I hover over it. adsf
  • A neatly defined generated.GetRepositoryRepository type. If you dislike the naming there, note that it’s <query-name><type-name>, which I think is the right decision.

Using go generate

The command that we want to run to generate code is

Terminal window
go run github.com/Khan/genqlient genqlient.yaml

But that’s a handful. Luckily go has the go generate command, which scans our Go files for compiler directives. We will add one to our main.go file:

//go:generate go run github.com/Khan/genqlient genqlient.yaml

Conclusion

While there are a few details I’m leaving out (see full example), this library does exactly what it says it does.

When I did come across problems, the error messages were helpful, and I wasn’t stuck for long. I would have no problem using it in production.

Wow! You read the whole thing. People who make it this far sometimes want to receive emails when I post something new.

I also have an RSS feed.