Stream of Consciousness

Mark Eschbach's random writings on various topics.

Building a minimal GraphQL service in Go

Categories: programming

Tags: golang graphql

I figured it’s time to get serious about learning Go. Primarily since there has been a major market shift in the past few years as well as some interesting design ramifications. Plus it will help me understand parts of Kubernetes a bit better when I read the source.

I was hoping to build a GraphQL service. There are many implementations of GraphQL bindings for Go.

  • github.com/graphql-go/graphql looks like a rather bare-bones implemenation.
  • graph-gophers/graphql-go looks promising since it supports contexts and OpenTracing out of the box, though the resolver system looks a bit strange.
  • 99designs/gqlgen has a codegen component which I am not entirely comfortable with at this point of my go adventures.
  • graphql-go/relay appears to be really rudimentary.
  • machinebox/graphql is a client.
  • samsarahq/thunder looks promising. Especially with the parallelism. In the future this might be one I experiment with.
  • appointy/jaal: This seems like an implemenation however I could not gather any real differentiating factors. Perahps in the future I will have a better understanding of the competitive advantage of this library.

After a brief 15 minute look at these, I think I’ll be going with graph-gophers/graphql-go for right now. Time to setup a simple project. GoLand has definitely improved since the last time I gave it a whirrl.

I dropped the example into a file title palatable.go and ran the following wiht go1.14 on teh path:

go mod init git.meschbach.com/mee/palatable
go build
./palatable 

Once I resolved the port conflict for tcp/8080 the service started fine. When I navigated to /query I received a simple response entity EOF. When running the following example query it worked as expected:

$ curl -XPOST -d '{"query": "{ hello }"}' localhost:8080/query
stdout>{"data":{"hello":"Hello, world!"}}

Now time to actually get something done with the system. I decided to extend the system to provide some user metadata since most of the systems I build for personal use eventually have this. In this case just simply asking if the user is authenticated via the following schema:

type Query {
        hello: String!
        user: UserQuery
}

type UserQuery {
  isAuthenticated: Boolean
}

Easy enough to implement via the following setup:

type UserQuery struct {

}

func (q *UserQuery) IsAuthenticated() bool  {
	return true;
}

type query struct{}

func (_ *query) Hello() string { return "Hello, world!" }
func (_ *query) User() *UserQuery {
	return &UserQuery{};
}

Or so I thought! When attempting to run the application I recieved the following:

$ ./palatable
stderr>panic: bool is not a pointer
stderr>        used by (*main.UserQuery).IsAuthenticated
stderr>        used by (*main.query).User
stderr>
stderr>goroutine 1 [running]:
stderr>github.com/graph-gophers/graphql-go.MustParseSchema(...)
stderr>        /Users/meschbach/.go/pkg/mod/github.com/graph-gophers/graphql-go@v0.0.0-20200309224638-dae41bde9ef9/graphql.go:57
stderr>main.main()
stderr>        /Users/meschbach/wc/mee/palatable/palatable.go:37 +0x18c

Turns out you can’t just take the address of a boolean (return &true) either. I found an example of how to convert a boolean to via a Pet Shope example’s helpers which is a bit odd. Perhaps this was the talking point about scalar support in the previous systems?

``golang func boolP(b bool) *bool { return &b }

func (q *UserQuery) IsAuthenticated() *bool { return boolP(true); } ``

Well that is a good start. Next time block I work on it I will be able to begin implementing actual domain logic.