Go Client¶
A fully typed GraphQL client generated from your schema. Types, inputs and enums are plain Go structs and string
types, every operation is a method taking a context.Context and returning (value, error), and the transport is a
single injectable function, so you can use the generated net/http adapter, your own, or a test double.
Requires Go 1.22+. The generated package has no dependency beyond the standard library, except the default
WebSocket adapter, which uses github.com/coder/websocket. With
"wsAdapter": "none" nothing outside the standard library is imported.
Configuration¶
Set "mode": "client" and add a "go" key under clientConfig:
Everything is generated into one Go package (packageName), flat in outputDir: GraphQL types may reference each
other in cycles, and Go does not allow import cycles between packages. All options are listed in the
configuration reference.
Using the client¶
client, err := graphql.NewGraphLinkClientWithHttp(graphql.GraphLinkHttpOptions{
URL: "https://api.example.com/graphql",
WsURL: "wss://api.example.com/graphql",
HeadersProvider: func(ctx context.Context) (map[string]string, error) {
return map[string]string{"Authorization": "Bearer token"}, nil
},
})
if err != nil {
return err
}
res, err := client.Queries.GetUser(ctx, "user-1")
if err != nil {
return err
}
fmt.Println(res.GetUser.Name)
Operations live on client.Queries, client.Mutations and client.Subscriptions, named after the schema field in
PascalCase. Required arguments are positional; optional arguments and arguments with a default go in a trailing
<Operation>Options struct, which you can leave out:
client.Queries.Greet(ctx) // defaults apply
client.Queries.Greet(ctx, graphql.GreetOptions{Name: graphql.Ptr("Bob")}) // override one
Bring your own adapter¶
The client only needs a function that takes the JSON payload and returns the response body:
adapter := func(ctx context.Context, payload string) (string, error) {
request, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(payload))
request.Header.Set("Content-Type", "application/json")
response, err := myHTTPClient.Do(request)
if err != nil {
return "", err
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
return string(body), err
}
client, err := graphql.NewGraphLinkClient(graphql.GraphLinkClientOptions{Adapter: adapter, WsAdapter: myWsAdapter})
With operationNameAsParameter: true the adapter also receives the operation name as a third argument.
WsAdapter is required when the schema has subscriptions (implement GraphLinkWebSocketAdapter, or use the generated
DefaultGraphLinkWebSocketAdapter).
Types¶
- Fields are exported
PascalCase; the GraphQL name lives in thejsontag, soencoding/jsonproduces and reads the wire format. - Nullable scalars, enums and objects are pointers (
*string,*Address); nullable lists and maps stay slices and maps (nilmeansnull). Usegraphql.Ptr(value)to build a pointer inline. - Go struct literals have no default values. For an input with schema defaults the generator emits
NewConfigInput(), which returns the struct with its defaults applied; a plainConfigInput{}sends zero values. Defaults on operation arguments are applied by the method when the option is omitted. - Interfaces and unions are Go interfaces (with an
Is<Name>()marker). Decoding picks the concrete struct from__typenameand returns it by value, so use a type switch:switch m := item.(type) { case graphql.Photo: ... }. An unknown__typenameis a decoding error. - A getter (
GetId()) is generated for every interface field. - Types with identity fields (
@glEqualsHashcodeoridentityFields) get anEqualsmethod. nullableFieldsRequiredandimmutableTypeFieldshave no effect in Go.
Enums¶
Enums are string types with constants prefixed by the enum name (graphql.UserStatusActive). Unless strictEnums
is true, a value the schema does not declare decodes to <Enum>UnknownValue__ instead of failing, and
encoding that value is an error.
Subscriptions¶
A subscription returns a channel of events:
events := client.Subscriptions.UserStatusChanged(ctx, "user-1")
for event := range events {
if event.Err != nil {
log.Println(event.Err) // does not close the channel; the adapter may reconnect
continue
}
fmt.Println(event.Data.UserStatusChanged.Status)
}
Cancel the ctx to stop: the client tells the server and closes the channel. The channel also closes when the server
completes the subscription. All subscriptions share one WebSocket connection; the default adapter reconnects with
exponential backoff and re-subscribes. Use AddReconnectListener on it to observe reconnects.
Errors¶
Operations return a *GraphLinkException (its Errors field is a []GraphLinkError) when the response contains
GraphQL errors; use errors.As to read it. Mark an operation with @glCaptureErrors (or set captureErrors: true) to
get a …FullResponse with Data and Errors instead.
Caching¶
@glCache and @glCacheInvalidate behave as in the other clients (see Caching). The default store is
in-memory; implement GraphLinkCacheStore (four methods) and pass it as Store to persist it. Stores and the tag locks
are safe for concurrent use.
File uploads¶
Upload arguments take GLUpload values; the multipart request follows the GraphQL multipart request spec:
file, err := graphql.GLUploadFromFile("avatar.png", "image/png")
res, err := client.Mutations.UploadAvatar(ctx, "user-1", file, graphql.UploadAvatarOptions{
OnProgress: func(sent, total int64) { fmt.Printf("%d/%d\n", sent, total) },
})
GLUploadFromBytes builds one from memory. The default adapter buffers the request body to know its total size, so
progress is exact; for very large files provide your own GLMultipartAdapter.
Persisted queries¶
persistedQueries: "apq" (and optionally persistedQueriesUseGet) work as described in
Persisted Queries. With GET enabled, NewGraphLinkClientWithHttp wires the GET adapter; when
building the client yourself set GetAdapter in GraphLinkClientOptions.
Known limitations¶
- Non-null cycles do not compile. Struct fields are plain values, so
type A { b: B! }withtype B { a: A! }gives a struct of infinite size. Make one side of the cycle nullable (it becomes a pointer). - Custom scalar packages.
typeMappingsmay nametime.Time,json.RawMessage,big.Int,url.URLoruuid.UUID, and GraphLink adds the import. Any other package must already be importable by that name from the generated package. - Interface getters with a different type.
Get<Field>()is generated for every interface field. If an implementing type declares the field with a type that differs from the interface's in more than nullability, the getter falls back to a plain return and the file may not compile. - No
gofmt. Generated files are not formatted. Rungofmt -won the output directory if you commit it. - Interface and union values are decoded by value. A decoded
Photois agraphql.Photo, never a*graphql.Photo, so a type switch on the pointer type never matches. - No default values on struct literals.
ConfigInput{}sends zero values. UseNewConfigInput()to get the schema defaults. - Unknown
__typenamefails the response. Unlike enums, there is no fallback for an interface or union member added to the server after the client was generated. - Encoding
<Enum>UnknownValue__is an error. The fallback is for reading only; see Enums. - Large uploads are buffered by the default adapter. Provide your own
GLMultipartAdapterto stream them. - Ignored options.
nullableFieldsRequiredandimmutableTypeFieldshave no effect.