Genqlient

Alex Woods

Alex Woods

Aug 18, 2022

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:

package main

import (
	"context"
	"fmt"
	"net/http"

	"github.com/Khan/genqlient/graphql"
	"github.com/alexhwoods/evaluate-genqlient/generated"
)

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

const GithubGraphqlAPI = "https://api.github.com/graphql"


func main() {
	key := "<personal-access-token>"

	ctx := context.Background()

  // I'm glossing over authentication - see full example
	client := graphql.NewClient(GithubGraphqlAPI, &httpClient)
	resp, err := generated.GetRepository(ctx, client, "kubernetes", "kubernetes")

	if err != nil {
		fmt.Printf("error: %s", err)
	}

	useRepository(resp.Repository)
}

func useRepository(repository generated.GetRepositoryRepository) {
	fmt.Printf("repository.Name: %v\n", repository.Name)
	fmt.Printf("repository.Description: %v\n", repository.Description)
	fmt.Printf("repository.StargazerCount: %v\n", repository.StargazerCount)
	fmt.Printf("repository.Url: %v\n", repository.Url)
}

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

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.

Want to know when I write a new article?

Get new posts in your inbox