Genqlient
Alex Woods
August 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 itgithub.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. - 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.