1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
|
# graphql-go [](https://sourcegraph.com/github.com/graph-gophers/graphql-go?badge) [](https://github.com/graph-gophers/graphql-go/actions/workflows/go.yml) [](https://goreportcard.com/report/github.com/graph-gophers/graphql-go) [](https://godoc.org/github.com/graph-gophers/graphql-go)
<p align="center"><img src="docs/img/logo.png" width="300"></p>
The goal of this project is to provide full support of the [October 2021 GraphQL specification](https://spec.graphql.org/October2021/) with a set of idiomatic, easy to use Go packages.
While still under development (`internal` APIs are almost certainly subject to change), this library is safe for production use.
## Features
- minimal API
- support for `context.Context`
- support for the `OpenTelemetry` and `OpenTracing` standards
- schema type-checking against resolvers
- resolvers are matched to the schema based on method sets (can resolve a GraphQL schema with a Go interface or Go struct).
- handles panics in resolvers
- parallel execution of resolvers
- inspect the selected fields and their args to prefetch data and avoid the N+1 query problem
- subscriptions
- [sample WS transport](https://github.com/graph-gophers/graphql-transport-ws)
## (Some) Documentation [](https://godoc.org/github.com/graph-gophers/graphql-go)
### Getting started
In order to run a simple GraphQL server locally create a `main.go` file with the following content:
```go
package main
import (
"log"
"net/http"
graphql "github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
)
type query struct{}
func (query) Hello() string { return "Hello, world!" }
func main() {
s := `
type Query {
hello: String!
}
`
schema := graphql.MustParseSchema(s, &query{})
http.Handle("/query", &relay.Handler{Schema: schema})
log.Fatal(http.ListenAndServe(":8080", nil))
}
```
Then run the file with `go run main.go`. To test:
```sh
curl -XPOST -d '{"query": "{ hello }"}' localhost:8080/query
```
For more realistic usecases check our [examples section](https://github.com/graph-gophers/graphql-go/wiki/Examples).
### Resolvers
A resolver must have one method or field for each field of the GraphQL type it resolves. The method or field name has to be [exported](https://golang.org/ref/spec#Exported_identifiers) and match the schema's field's name in a non-case-sensitive way.
You can use struct fields as resolvers by using `SchemaOpt: UseFieldResolvers()`. For example,
```
opts := []graphql.SchemaOpt{graphql.UseFieldResolvers()}
schema := graphql.MustParseSchema(s, &query{}, opts...)
```
When using `UseFieldResolvers` schema option, a struct field will be used *only* when:
- there is no method for a struct field
- a struct field does not implement an interface method
- a struct field does not have arguments
The method has up to two arguments:
- Optional `context.Context` argument.
- Mandatory `*struct { ... }` argument if the corresponding GraphQL field has arguments. The names of the struct fields have to be [exported](https://golang.org/ref/spec#Exported_identifiers) and have to match the names of the GraphQL arguments in a non-case-sensitive way.
The method has up to two results:
- The GraphQL field's value as determined by the resolver.
- Optional `error` result.
Example for a simple resolver method:
```go
func (r *helloWorldResolver) Hello() string {
return "Hello world!"
}
```
The following signature is also allowed:
```go
func (r *helloWorldResolver) Hello(ctx context.Context) (string, error) {
return "Hello world!", nil
}
```
### Separate resolvers for different operations
This feature was released in `v1.6.0`.
The GraphQL specification allows for fields with the same name defined in different query types. For example, the schema below is a valid schema definition:
```graphql
schema {
query: Query
mutation: Mutation
}
type Query {
hello: String!
}
type Mutation {
hello: String!
}
```
The above schema would result in name collision if we use a single resolver struct because fields from both operations correspond to methods in the root resolver (the same Go struct). In order to resolve this issue, the library allows resolvers for query, mutation and subscription operations to be separated using the `Query`, `Mutation` and `Subscription` methods of the root resolver. These special methods are optional and if defined return the resolver for each opeartion. For example, the following is a resolver corresponding to the schema definition above. Note that there is a field named `hello` in both the query and the mutation definitions:
```go
type RootResolver struct{}
type QueryResolver struct{}
type MutationResolver struct{}
func(r *RootResolver) Query() *QueryResolver {
return &QueryResolver{}
}
func(r *RootResolver) Mutation() *MutationResolver {
return &MutationResolver{}
}
func (*QueryResolver) Hello() string {
return "Hello query!"
}
func (*MutationResolver) Hello() string {
return "Hello mutation!"
}
schema := graphql.MustParseSchema(sdl, &RootResolver{}, nil)
...
```
### Schema Options
- `UseStringDescriptions()` enables the usage of double quoted and triple quoted. When this is not enabled, comments are parsed as descriptions instead.
- `UseFieldResolvers()` specifies whether to use struct field resolvers.
- `MaxDepth(n int)` specifies the maximum field nesting depth in a query. The default is 0 which disables max depth checking.
- `MaxParallelism(n int)` specifies the maximum number of resolvers per request allowed to run in parallel. The default is 10.
- `Tracer(tracer trace.Tracer)` is used to trace queries and fields. It defaults to `noop.Tracer`.
- `Logger(logger log.Logger)` is used to log panics during query execution. It defaults to `exec.DefaultLogger`.
- `PanicHandler(panicHandler errors.PanicHandler)` is used to transform panics into errors during query execution. It defaults to `errors.DefaultPanicHandler`.
- `DisableIntrospection()` disables introspection queries.
- `DisableFieldSelections()` disables capturing child field selections used by helper APIs (see below).
- `OverlapValidationLimit(n int)` sets a hard cap on examined overlap pairs during validation; exceeding it emits `OverlapValidationLimitExceeded` error.
### Field Selection Inspection Helpers
Resolvers can introspect which immediate child fields were requested using:
```go
graphql.SelectedFieldNames(ctx) // []string of direct child schema field names
graphql.HasSelectedField(ctx, "name") // bool
graphql.SortedSelectedFieldNames(ctx) // sorted copy
```
Use cases include building projection lists for databases or conditionally avoiding expensive sub-fetches. The helpers are intentionally shallow (only direct children) and fragment spreads / inline fragments are flattened with duplicates removed; meta fields (e.g. `__typename`) are excluded.
Performance: selection data is computed lazily only when a helper is called. If you never call them there is effectively no additional overhead. To remove even the small context value insertion you can opt out with `DisableFieldSelections()`; helpers then return empty results.
For more detail and examples see the [docs](https://godoc.org/github.com/graph-gophers/graphql-go).
### Custom Errors
Errors returned by resolvers can include custom extensions by implementing the `ResolverError` interface:
```go
type ResolverError interface {
error
Extensions() map[string]interface{}
}
```
Example of a simple custom error:
```go
type droidNotFoundError struct {
Code string `json:"code"`
Message string `json:"message"`
}
func (e droidNotFoundError) Error() string {
return fmt.Sprintf("error [%s]: %s", e.Code, e.Message)
}
func (e droidNotFoundError) Extensions() map[string]interface{} {
return map[string]interface{}{
"code": e.Code,
"message": e.Message,
}
}
```
Which could produce a GraphQL error such as:
```go
{
"errors": [
{
"message": "error [NotFound]: This is not the droid you are looking for",
"path": [
"droid"
],
"extensions": {
"code": "NotFound",
"message": "This is not the droid you are looking for"
}
}
],
"data": null
}
```
### Tracing
By default the library uses `noop.Tracer`. If you want to change that you can use the OpenTelemetry or the OpenTracing implementations, respectively:
```go
// OpenTelemetry tracer
package main
import (
"github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/example/starwars"
otelgraphql "github.com/graph-gophers/graphql-go/trace/otel"
"github.com/graph-gophers/graphql-go/trace/tracer"
)
// ...
_, err := graphql.ParseSchema(starwars.Schema, nil, graphql.Tracer(otelgraphql.DefaultTracer()))
// ...
```
Alternatively you can pass an existing trace.Tracer instance:
```go
tr := otel.Tracer("example")
_, err = graphql.ParseSchema(starwars.Schema, nil, graphql.Tracer(&otelgraphql.Tracer{Tracer: tr}))
```
```go
// OpenTracing tracer
package main
import (
"github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/example/starwars"
"github.com/graph-gophers/graphql-go/trace/opentracing"
"github.com/graph-gophers/graphql-go/trace/tracer"
)
// ...
_, err := graphql.ParseSchema(starwars.Schema, nil, graphql.Tracer(opentracing.Tracer{}))
// ...
```
If you need to implement a custom tracer the library would accept any tracer which implements the interface below:
```go
type Tracer interface {
TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, func([]*errors.QueryError))
TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, func(*errors.QueryError))
TraceValidation(context.Context) func([]*errors.QueryError)
}
```
### [Examples](https://github.com/graph-gophers/graphql-go/wiki/Examples)
|