What is genqlient?
maininterface{} for unmarshaling results, ensuring that queries are valid against the schema before deployment.repository·main·Indexed 23 days ago
https://github.com/khan/genqlientA code generation tool for Go that creates type-safe GraphQL clients. It validates GraphQL queries against a schema at compile-time and generates corresponding Go structs for response unmarshaling, eliminating the need for manual struct definitions or the use of interface{}.
interface{} for unmarshaling results, ensuring that queries are valid against the schema before deployment.In GraphQL, types can be optional (nullable) or non-optional (e.g., String!). Because Go does not have a native way to distinguish between a zero value (like "") and a null value for basic types, libraries often use pointers (*string).
genqlient's approach: By default, genqlient does not use pointers for optionality to maintain idiomatic Go style. This means it may be difficult to distinguish between a field being set to its zero value versus being null. However, the library is designed to allow for future configuration options to enable pointers (for distinguishing zero vs. null) or presence fields.
Genqlient follows semantic versioning, but because it is currently in 0.x versioning, breaking changes may occur. The project aims to limit breaking changes to minor version bumps (e.g., 0.6.0 instead of 0.5.1).
graphql package.graphql runtime package that require corresponding changes to the code-generator.__name) in generated code or names in the graphql runtime documented as "intended for the use of genqlient's generated code only".graphql runtime package.Your version of the graphql runtime package must be the same major version and the same or newer than your version of the code-generator. It is recommended to use the same version for both.
By default, genqlient maps standard GraphQL scalars to Go types as follows:
| GraphQL type | Go type |
|---|---|
Int | int |
Float | float64 |
String | string |
Boolean | bool |
ID | string |
To handle custom scalars or to override these defaults (e.g., changing Int to int32), use the bindings option in your genqlient.yaml file.
When querying an interface, genqlient generates a Go interface for the interface type and specific structs for each implementation.
If you only want to request shared fields and do not need fragments, you can use # @genqlient(struct: true) on the interface field to skip interface generation and just get a plain struct.
// Using type switches for interface implementations
resp, err := GetBooks(...)
fmt.Println("Favorite book:", resp.Favorite.GetTitle())
if novel, ok := resp.Favorite.(*GetBooksFavoriteNovel); ok {
fmt.Println("Protagonist:", novel.Protagonist)
}Every generated helper function returns a pointer to a struct that mirrors the shape of your GraphQL query result. For example, a query for a user with a name field will generate a response struct containing a User field, which in turn contains a Name string.
// Example generated structure for:
// query getUser($login: String!) { user(login: $login) { name } }
func getUser(...) (*getUserResponse, error) { ... }
type getUserResponse struct {
User getUserUser
}
type getUserUser struct {
Name string
}By default, genqlient maps GraphQL null values to Go zero values (e.g., null string becomes ""). If you need to distinguish between a null value and a zero value, you have three options:
null to the type's zero value (e.g., 0 for int, "" for string). For structs, the entire struct is set to its zero value.@genqlient(pointer: true) directive to map null to nil. This generates a pointer type (e.g., *string).Option<T>) by configuring optional: generic and optional_generic_type in your genqlient.yaml. This generates a type like Option[string].# genqlient.yaml configuration for generics
optional: generic
optional_generic_type: github.com/path/to/your/package.Optiongenqlient handles fragments using two different strategies depending on whether the fragment is inline or named:
... on T { ... }): These are flattened into the parent struct. This simplifies the resulting types and makes them easier to use without extra layers of nesting.fragment F on T { ... }): These are embedded as separate structs. This allows for better code deduplication; if a fragment is used in multiple places, you can write a function that accepts the fragment's type as an argument.Summary of strategies:
Genqlient allows you to configure how the generated query functions are structured, specifically regarding how they handle context.Context and the GraphQL Client.
You can configure the following:
context.Context.graphql.Client. This can be via a global client or by providing a hook/function to retrieve the client from the context (e.g., using context.Value or a custom method).This flexibility allows the generated code to integrate with various dependency injection patterns or custom context implementations used in your project.
// Example of possible generated signatures:
// Uses a standard context
func GetUser(ctx context.Context, id string) (*GetUserResponse, error)
// Uses a custom context
func GetUser(ctx mypkg.Context, id string) (*GetUserResponse, error)
// Uses a client object
func GetUser(client graphql.Client, id string) (*GetUserResponse, error)
// Uses both
func GetUser(ctx context.Context, client graphql.Client, id string) (*GetUserResponse, error)When a GraphQL query returns an interface, genqlient uses Go interfaces to represent the result. This is considered the most natural translation of GraphQL to Go.
How it works:
type I interface { isI() }).type T struct { ... }).func (t T) isI() {}).Usage: To access fields on an interface, you typically use a type switch to determine the concrete type, or you can use getter methods if they are provided by the interface.
// Example of interface representation
// GraphQL: interface I { b: String } | type T implements I { b: String, c: String } | type U implements I { b: String }
type I interface {
isI()
GetB() string
}
type T struct {
B string
C string
}
func (t T) isI() {}
func (t T) GetB() string { return t.B }
type U struct {
B string
}
func (u U) isI() {}
func (u U) GetB() string { return u.B }
type Response struct {
A I
}By default, genqlient uses the schema's field names for Go struct fields. You can customize these names by using GraphQL field aliases. genqlient will uppercase the alias to ensure the field is exported and visible to the Go JSON library.
Example:
Using myGreatName: myString in your query will result in a Go field named MyGreatName.
query MyQuery {
myGreatName: myString
}In traditional Go GraphQL clients, developers often manually define query strings and response structs. This approach has several risks:
map[string]interface{}, allowing for type errors that only surface at runtime.genqlient solves this by allowing you to simply specify the query; it then automatically validates the query against the schema and generates the necessary type-safe Go helpers and structs.
// Example of the manual, less safe approach used by other clients:
query := `query GetUser($id: ID!) { user(id: $id) { name } }`
variables := map[string]interface{}{"id": "123"}
var resp struct {
Me struct {
Name graphql.String
}
}
client.Query(ctx, query, &resp, variables)
fmt.Println(resp.Me.Name)