A GraphQL client package written in Go.
Please read the CONTRIBUTING.md document for guidelines on developing and contributing changes.
goql is a GraphQL client library with built-in two-way marshaling support via struct tags. This is key because it allows for strongly typed GraphQL queries as opposed to variables containing a string representation of the query. This also facilitates more advanced features, such as sparse field sets.
For complete documentation see the generated pkg.go documentation. For a complete guide on the struct tag syntax, see the documentation found below under Defining GraphQL Operations.
In the root of your project repository (same directory as your go.mod
and go.sum
files):
go get github.com/getoutreach/goql
After that you should be able to import it anywhere within your project.
GraphQL operations can be defined by using normal Go struct types along with the help of struct tags. For example:
type QueryUserCollection struct {
UserCollection struct {
Collection []struct {
ID string
Name string
} `goql:"keep"`
} `goql:"userCollection(filter:$filter<[Filter]>,sort:$sort<[String]>,size:$size<Int>,before:$before<String>,after:$after<String>)"`
}
when passed through the GraphQL query marshaller renders the following string:
query (
$filter: [Filter]
$sort: [String]
$size: Int
$before: String
$after: String
) {
userCollection(
filter: $filter
sort: $sort
size: $size
before: $before
after: $after
) {
collection {
id
name
}
}
}
Here's the high-level steps to go through when first defining a GraphQL operation:
QueryUserCollection
's only immediate child is UserCollection
which
together represents the query($filter: [Filter], ...) { userCollection(filter: $filter, ...) { ... } }
part of the
output).UserCollection
contains children fields []Collection
, ID
, and Name
). All types should match the types described
in the schema of the query. - ID
in GraphQL is a string
in Go. - Any type with the non-null (!
) restriction in GraphQL should be a non-pointer type in Go. Conversely, any type
in GraphQL without this restriction should be nullable (a pointer type) in Go. - If the field is an integral part of the operation, e.g. UserCollection
, and Collection
fields in the struct
above, add the goql:"keep"
tag to them to tell the marshaler to always include these fields. This is necessary
in order for sparse field sets to work. However, in the example above the keep tag can actually be omitted from the
UserCollection
part of the query as it already defines an operation declaration, which the marshaler already sees
as an integral part of the operation and implicitly marks it to be kept (that is why the keep
tag is left off of
that portion, but on Collection
still).goql
struct tags to further define the structure of the operation by
modifying declarations, adding aliases, variables, or directives to each field. See the immediately proceeding section,
GraphQL Struct Tag Syntax, for more information on these struct tags and how to define
them.The following components can be used alone or together, separated by a comma within in the tag, to define a goql
struct tag for a field or model on an operation:
modelName(arg:$var<Type>, arg2:$var2<Type2!>, ...)
MyModel struct `goql:"myModel(page:$page<Int!>)"`
-> query($page: Int!) { myModel(page: $page) { ...
fieldNameOverride
Name string `goql:"username"`
-> username
@alias(desiredAlias)
Role *Role `goql:"createRole(...)"`
would need an
alias since createRole (operation name) != Role (struct field name).Name string `goql:"@alias(username)"`
-> username: name
@include($flag)
Boolean!
, so it is implied and therefore not
necessary.Name string `goql:"@include($withName)"`
-> name @include(if: $withName)
@skip($flag)
Boolean!
, so it is implied and therefore not
necessary.Name string `goql:"@skip($withoutName)"`
-> name @skip(if: $withoutName)
keep
Here is an example of using multiple struct tags together:
Name string `goql:"@alias(username),@include($withName)"`
-> username: name @include(if: $withName)
Rules:
Name string `goql:"@include($withName),@include($withName2)"`
would result in an error because an include
directive was defined twice on the same struct tag.MyModel struct `goql:"myModel(page:$page<Int!>,pageSize:$page<Int>)"`
would result in an error, since
$page is defined to have both the type of Int!
and Int
.MyModel struct `goql:"myModel(page:$page<Int!>),@include($page)"`
would also result in an error, since
$page is defined to have the type of both Int!
and Boolean!
(implicit when used in the include directive).