The goal of this project is to provide full support of the GraphQL draft specification with a set of idiomatic, easy to use Go packages.
This project is still under heavy development and APIs are almost certainly subject to change.
- minimal API
- support for
context.Context
- support for the
OpenTracing
standard - schema type-checking against resolvers
- custom resolvers
- built int resolvers against maps, struct fields, interface methods
- handles panics in resolvers
- parallel execution of resolvers
- modifying schemas and generating new schema documents
- relay compatible http interface
package main
import (
"fmt"
"github.com/chirino/graphql"
"github.com/chirino/graphql/relay"
"github.com/friendsofgo/graphiql"
"log"
"net/http"
)
type query struct {
Name string `json:"name"`
}
func (q *query) Hello() string { return "Hello, " + q.Name }
func main() {
engine := graphql.New()
engine.Root = &query{
Name: "World!",
}
err := engine.Schema.Parse(`
schema {
query: Query
}
type Query {
name: String!
hello: String!
}
`)
if err != nil {
log.Fatal(err)
}
http.Handle("/graphql", &relay.Handler{Engine: engine})
fmt.Println("GraphQL service running at http://localhost:8080/graphql")
http.Handle("/", graphiql.New("ws://localhost:8080/graphql", true))
fmt.Println("GraphiQL UI running at http://localhost:8080/")
log.Fatal(http.ListenAndServe(":8080", nil))
}
To test:
$ curl -XPOST -d '{"query": "{ hello }"}' localhost:8080/query
Or open http://localhost:8080/graphiql
and use the graphiql UI.
- Step 1:
engine := graphql.New()
: Create the GraphQL engine - Step 2:
engine.Root = ...
: Associate a root object that the resolvers will operate against with the call.
If you use custom resolvers this may not be required, but all the default resolvers, navigate this Root object. - Step 3:
err := engine.Schema.Parse(...)
: Configure the engine to know what the valid graphql operations that can be performed using theGraphQL schema language
. - Step 4:
http.Handle("/graphql", &relay.Handler{Engine: engine})
: Use a relay http interface to access the GraphQL engine. - Step 5:
log.Fatal(http.ListenAndServe(":8080", nil))
: Start the http server
The example also starts a graphiql UI endpoint at http://localhost:8080/
which is optional. It gives you a nice
UI interface to introspect your graphql endpoint and test it out.
You can call err := engine.Schema.Parse(...)
multiple times to evolve the schema. By default any types
redeclared will replace the previous definition. You can use engine.Schema.String() to get the schema back in the
GraphQL schema language
.
You can use a @graphql(alter:"add")
directive to add fields, directives, or interfaces to previously defined type.
For example, lets say your previously parsed:
type Person {
name: String
}
If you then subsequently parse:
type Person @graphql(alter:"add") {
children: [Person!]
}
then the resulting schema would be:
type Person {
name: String
children: [Person!]
}
Similarly you can use @graphql(alter:"drop")
to remove fields, directives, or interfaces from a type.
Resolvers implement accessing that data selected by the GraphQL query or mutations.
When a graphql field is access, we the graphql engine tries using each of the following resolvers until one of them is able to resolve field:
resolvers.MetadataResolver
: Resolves the__typename
,__schema
, and__type
metadata fields.resolvers.MethodResolver
: Resolves the field using exported functions implemented on the current structresolvers.FieldResolver
: Resolves the field using exported fields on the current structresolvers.MapResolver
: Resolves the field using map entries on the current map.
If no resolver can resolve the GraphQL field, then it results in a GraphQL error.
The method name has to be exported and match the field's name in a non-case-sensitive way.
The method has up to two arguments:
- Optional
context.Context
argument orresolvers.ExecutionContext
. - Mandatory
*struct { ... }
argument if the corresponding GraphQL field has arguments. The names of the struct fields have to be exported 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:
func (r *helloWorldResolver) Hello() string {
return "Hello world!"
}
The following signature is also allowed:
func (r *helloWorldResolver) Hello(ctx resolvers.ExecutionContext) (string, error) {
return "Hello world!", nil
}
The method name has to be exported. The GraphQL field's name
must match the struct field name or the name in a json:""
annotation.
Example of a simple resolver struct:
type Query struct {
Name string
Length float64 `json:"length"`
}
Could be access with the following GraphQL type:
type Query {
Name: String!
length: Float!
}
You must use maps with string keys.
Example of a simple resolver map:
query := map[string]interface{}{
"Name": "Millennium Falcon",
"length": 34.37,
}
Could be access with the following GraphQL type:
type Query {
Name: String
length: Float
}
You can change the resolvers used by the engine and implement custom resolvers if needed.
Changing the default list of resolvers so that only metadata and method based resolvers are used:
engine.ResolverFactory = &resolvers.ResolverList{
resolvers.MetadataResolver,
resolvers.MethodResolver,
}
You can also implement a custom resolver. Here's an example resolver, that would resolve all foo
GraphQL
fields to the value "bar"
:
type MyFooResolver struct{}
func (this *MyFooResolver) Resolve(request *resolvers.ResolveRequest, prev resolvers.Resolution) resolvers.Resolution {
if request.Field.Name!="foo" {
return prev // Lets only handle the foo fields:
}
return func() (reflect.Value, error) {
return reflect.ValueOf("bar"), nil
}
}
Notice that the Resolve
method accepts a next resolvers.Resolution
argument. If it is not nil,
then it is the resolution for the field of a Resolver that was executed before your Resolver. Your
resolver can use this resolution to filter or transform the result of the field. Here's an example of
a resolver that adds a prefix to result:
func (this *MyFooResolver) Resolve(request *resolvers.ResolveRequest, prev resolvers.Resolution) resolvers.Resolution {
if next == nil {
return nil
}
return func() (reflect.Value, error) {
value, err := next()
if err != nil {
return value, err
}
v := fmt.Sprintf("Hello %s", value.String())
return reflect.ValueOf(v), nil
}
}
If resolvers are going to fetch data from multiple remote systems, you will want to resolve those async so that each fetch is done in parallel. You can enable async resolution in your custom resolver using the following pattern:
type MyHttpResolver struct{}
func (this *MyHttpResolver) Resolve(request *resolvers.ResolveRequest, next resolvers.Resolution) resolvers.Resolution {
if request.Field.Name!="google" {
return next // Lets only handle the google field:
}
return request.RunAsync(func() (reflect.Value, error) {
httpClient := &http.Client{}
req, err := http.NewRequest("GET", "http://google.com", nil)
if err!=nil {
return nil, error
}
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return reflect.ValueOf(string(data)), nil
})
}
Implementing graphql subscriptions require a special type of resolver which issue
FireSubscriptionEvent
method calls when new event notifications need to be sent to
the GraphQL client which using the
subscriptions-transport-ws
module to receive subscription events.
Below is a simple example that makes a hello
subscription which takes a duration argument
that controls how often a Hello
event is generated and sent to the client.
package main
import (
"fmt"
"github.com/chirino/graphql"
"github.com/chirino/graphql/graphiql"
"github.com/chirino/graphql/relay"
"github.com/chirino/graphql/resolvers"
"log"
"net/http"
"reflect"
"time"
)
type root struct {
Test string `json:"test"`
}
func (m *root) Hello(ctx resolvers.ExecutionContext, args struct{ Duration int }) {
go func() {
counter := args.Duration
for {
select {
case <-ctx.GetContext().Done():
// We could close any resources held open for the subscription here.
return
case <-time.After(time.Duration(args.Duration) * time.Millisecond):
// every few duration ms.. fire a subscription event.
ctx.FireSubscriptionEvent(reflect.ValueOf(fmt.Sprintf("Hello: %d", counter)))
counter += args.Duration
}
}
}()
}
func main() {
engine := graphql.New()
engine.Root = &root{ Test:"Hi!" }
err := engine.Schema.Parse(`
schema {
query: MyQuery
subscription: MySubscription
}
type MyQuery {
test: String
}
type MySubscription {
hello(duration: Int!): String
}
`)
if err != nil {
log.Fatal(err)
}
http.Handle("/graphql", &relay.Handler{Engine: engine})
fmt.Println("GraphQL service running at http://localhost:8080/graphql")
http.Handle("/", graphiql.New("ws://localhost:8080/graphql", true))
fmt.Println("GraphiQL UI running at http://localhost:8080/")
log.Fatal(http.ListenAndServe(":8080", nil))
}
You can use directives defined on the GraphQL schema to attach and configure resolvers. Full Example:
package main
import (
"fmt"
"github.com/chirino/graphql"
"github.com/chirino/graphql/graphiql"
"github.com/chirino/graphql/relay"
"github.com/chirino/graphql/resolvers"
"log"
"net/http"
"reflect"
)
type query struct {
Name string `json:"name"`
}
func main() {
engine := graphql.New()
engine.Root = &query{
Name: "Hiram",
}
err := engine.Schema.Parse(`
directive @static(prefix: String) on FIELD
schema {
query: Query
}
type Query {
name: String! @static(prefix: "Hello ")
}
`)
// Lets register a resolver that is applied to fields with the @static directive
engine.Resolver = resolvers.List(engine.Resolver, resolvers.DirectiveResolver{
Directive: "static",
Create: func(request *resolvers.ResolveRequest, next resolvers.Resolution, args map[string]interface{}) resolvers.Resolution {
// This resolver just filters the next result bu applying a prefix..
// so we need the next resolver to be valid.
if next == nil {
return nil
}
return func() (reflect.Value, error) {
value, err := next()
if err != nil {
return value, err
}
v := fmt.Sprintf("%s%s", args["prefix"], value.String())
return reflect.ValueOf(v), nil
}
},
})
if err != nil {
log.Fatal(err)
}
http.Handle("/graphql", &relay.Handler{Engine: engine})
fmt.Println("GraphQL service running at http://localhost:8080/graphql")
http.Handle("/", graphiql.New("ws://localhost:8080/graphql", true))
fmt.Println("GraphiQL UI running at http://localhost:8080/")
log.Fatal(http.ListenAndServe(":8080", nil))
}
- We love pull requests
- Having problems? Open an issue
- This project is written in Go. It should work on any platform where go is supported.
- setup ci jobs to validate PRs
- provide better hooks to implement custom directives so you can do things like configuring/selecting resolvers using directives. Or using directives to drive schema generation.
- provide dataloader functionality
This is a fork of the http://github.com/graph-gophers/graphql-go project.