At 99designs we’ve been on a journey to deconstruct our PHP monolith into a microservice architecture, with most new services being written in Go. During this period, our front-end team also adopted type safety, transitioning from Javascript to TypeScript & React.

gqlgen logo
gqlgen logo by V’Official

By having type safety in our backend and frontend, it became apparent that our bespoke REST endpoints were not able to bridge the type-gap. We needed a way to join these type systems together and untangle our API endpoints.

What we needed was a type-safe system for APIs. GraphQL looked promising. As we explored it, however, we realized that there wasn’t a server approach out there that met all of our needs. So we developed our own, which we call gqlgen.

What is GraphQL?

GraphQL is a query language for APIs that gives a complete and understandable description of data, and gives clients the power to ask for exactly what they need (and not get anything extraneous).

For example, we can define types: say a User has some fields, mostly scalars like name and height, but also of other complex types like location.

Unlike REST, we query a GraphQL endpoint by describing the shape of the result:

{ 
    user(id: 10) { 
        name, 
        location {lat, long}
    }
}

Fields can take arguments that operate similar to query params, and these can be at any level of the graph.

From the above query the server returns:

{
    "user": {
        "name": "Bob",
        "location": {
            "lat": 123.456789,
            "lon": 123.456789
        }
    }
}

This is powerful because it gives us a shared type system both the client and server can understand, while also giving us amazing reusability. What if we wanted to plot our 3 best friends’ locations on a different screen?

{ 
    user(id: 10) { 
        friends(limit: 3) {
            name, 
            location {lat, long}
        }
    }
}

and we would get back

{
    "user": {
        "friends": [
            {
                "name": "Carol",
                "location": {"lat": 1, "lon": 1}
            }, 
            {
                "name": "Carlos",
                "location": {"lat": 2, "lon": 2}
            },
            {
                "name": "Charlie",
                "location": {"lat": 3, "lon": 3}
            },
        ]
    }
}

Goodbye bespoke endpoints, hello type-safe, discoverable, consistent APIs!

How does gqlgen compare to other GraphQL server approaches?

The first thing you need to do when you decide to use GraphQL is to decide which server library to use. Turns out there are a few different approaches to defining types and executing queries, the main roles of our GraphQL server.

Defining types

The first thing we need to do for any GraphQL server is define the types. This allows the server to validate incoming requests and provide introspection APIs that can power autocomplete and other useful features. There are three main approaches to defining types:

1. Custom domain specific language

You can build up the type tree directly in your programming language of choice. This is the easiest to implement for the server library, but often results in a lot of code for the user to write. DSLs work great in some languages, but in Go they are very verbose:

var queryType = graphql.NewObject(graphql.ObjectConfig{
	Name: "Query",
	Fields: graphql.Fields{
		"brief": &graphql.Field{
			Type: briefType,
			Args: graphql.FieldConfigArgument{
				"id": &graphql.ArgumentConfig{
					Type: graphql.NewNonNull(graphql.String),
				},
			},
		},
	},
})

The graphql-js reference implementation uses this approach, and many server implementations have followed suit, making this the most common approach. Being entirely dynamic means you can define a schema on the fly based on dynamic input. This isn’t a common requirement, but if you need it, it’s the only way to go.

Disadvantages
  • Loss of (compile-time) type safety: heavy use of open interface{} and reflection.
  • Mixing declarative schema definition code with imperative resolver code, making dependency injection hard.
  • The schema definition code is incredibly verbose when compared to the purpose-built Schema Definition Language.

This approach is usually very tedious and error-prone and leaves you with something that isn’t particularly readable. It gets even worse when there are loops in your graphs.

Used by graphql-go/graphql

2. Schema first

Compare the above DSL with the equivalent Schema Definition Language (SDL):

type Query {
    brief(id: String!): Brief
}

Short, concise and easy to read. This is also language agnostic, so your frontend team can use mocks generated from the SDL to quickly spin up a server that answers queries and start building client code concurrently with the server.

Used by 99designs/gqlgenprisma/graphqlgen and graph-gophers/graphql.

3. Reflection

This approach involves the least work as we don’t need to declare the GraphQL types explicitly at all. Instead, we can reflect the types from our language and build the GraphQL server from that.

Reflection sounds pretty good on paper, but if you want to use the full gamut of GraphQL features, you need to use a language that maps very closely to GraphQL. Automatically building interfaces and unions on top of a duck typed language is hard.

That being said, reflection is used to great effect in the graphql-ruby library:

class Types::ProfileType > Types::BaseObject
  field :id, ID, null: false
  field :name, String, null: false
  field :avatar, Types::PhotoType, null: true
end

While this may work well for languages like Ruby (where DSLs are commonplace), Go’s restrictive type system limits the power of this approach.

Used by samsarahq/thunder

Executing queries

Now that we know what we are exposing, now we need to write some code to answer these GraphQL queries. Each step in the GraphQL execution phase wants to call a function that looks roughly like this:

Execute('Query.brief', brief, {id: "123"}) -> Brief

Again, there are a couple approaches to executing these queries:

1. Expose a generic function signature

The most direct approach is to expose a generic function signature directly to the user, and let them handle everything.

var queryType = graphql.NewObject(graphql.ObjectConfig{
	Name: "Query",
	Fields: graphql.Fields{
		"brief": &graphql.Field{
			// other props are here but not important right now

			Resolve: func(p graphql.ResolveParams) (interface{}, error) {
				return mydb.FindBriefById(p.Args["id"].(string))
			},
		},
	},
})

There are a few issues here:

  • We need to deal with unpacking args ourselves from a map[string]interface{}
  • id might not be a string
  • Is it the correct return type?
  • Even if it is the correct type, does it have the correct fields?
  • How do I inject dependencies such as a database connection?

The library can validate the result at runtime, and extensive unit testing would catch these issues.

Again, we can declare new resolvers and types at runtime without recompiling. If that’s a feature you need, you probably want this kind of approach.

Used by graphql-go/graphql and graphql-js.

2. Runtime reflection for types

We can let the user define the functions themselves with the types they expect and use reflection to validate the everything is correct.

type query struct{
    db *myDb
}

func (q *query) Brief(id string) BriefResolver { 
    return briefResolver{db, q.db.FindBriefById(id)}
}

type briefResolver struct {
    db *myDb
    *db.Brief
}

func (b *briefResolver) ID() string { return b.ID }
func (b *briefResolver) State() string { return b.State }
func (b *briefResolver) UserID() string { return b.UserID }

This reads a bit nicer, the library has done all the unpacking logic for us and we can inject dependencies. But child resolvers need to have their dependencies injected manually. There is also no compile time safety so this job falls to runtime checks. At least this time we can statically verify the whole graph on boot instead of needing 100% code coverage just to catch issues.

Used by graph-gophers/graphql-go and samsarahq/thunder

Building gqlgen

As we explored GraphQL, we tried both graphql-go/graphql and graph-gophers/graphql-go in various projects. What we found was that graph-gophers/graphql-go has a better type system, but it wasn’t completely meeting our needs. We decided to try and incrementally make it more usable.

Generating resolver interfaces

Since Go 1.4 there has been first-class support for generating code via go generate, but none of the existing GraphQL servers were taking advantage of that. We realized that instead of doing runtime checks we could generate interfaces for the resolvers and the compiler could check that everything was implemented correctly.

// in generated code
type QueryResolver interface {
	Brief(ctx context.Context, id string) (*Brief, error)
}

type Brief struct {
    ID string
    State string
    UserID int
}
// in our code
type queryResolver struct{
    db *myDb
}

func (r *queryResolver) Brief(ctx context.Context, id string) (*Brief, error) {
    b, err :=  q.db.FindBriefById(id)
    if err != nil {
        return nil, err
    }
    return Brief {
        ID: b.ID,
        State: b.State,
        UserID: b.UserID,
    }, nil
}

Great! Now our compiler could tell us when our resolver signatures didn’t match our GraphQL schema. We also switched to a more MVC-like approach, where the resolver graph is static and dependencies can be injected once at boot time, rather then need to be injected into every node.

Binding to models

Even after generating type safe resolver interfaces, we were still writing a bit of manual mapper code. What if we let the code generator inspect our existing database model to see if it fit the GraphQL schema? If it did, we could use that type directly in the resolver signatures.

// in generated code
type QueryResolver interface {
	Brief(ctx context.Context, id string) (*db.Brief, error)
}
// in our code
type queryResolver struct{
    db *myDb
}

func (r *queryResolver) Brief(ctx context.Context, id string) (*db.Brief, error) {
    return q.db.FindBriefById(id)
}

Perfect. Now our resolver code was really just type safe glue! This works great for exposing databases models or even well-typed API clients (protobuf, Thrift) over GraphQL.

But what should happen to fields that don’t exist on the database model? Let’s generate another resolver.

// in generated code
type BriefResolver interface {
	Owner(ctx context.Context, *db.Brief) (*db.User, error)
}
// in our code
type briefResolver struct{
    db *myDb
}

func (r *briefResolver) Owner(ctx context.Context, brief *db.Brief) (*db.User, error) {
    return q.db.FindUserById(brief.OwnerID)
}

Generate marshalling and execution code

We have written almost no boilerplate and have complete type safety in our resolvers! But most of the execution phase is still using the original reflection system from graph-gophers and reflection is never clear. Let’s replace the reflection-based argument unpacking and resolver call logic with generated code:

func (ec *executionContext) _Brief(ctx context.Context, sel ast.SelectionSet, obj *model.Brief) graphql.Marshaler {
	fields := graphql.CollectFields(ctx, sel, briefImplementors)

	out := graphql.NewOrderedMap(len(fields))
	for i, field := range fields {
		out.Keys[i] = field.Alias

		switch field.Name {
		case "__typename":
			out.Values[i] = graphql.MarshalString("Brief")
		case "id":
            out.Values[i] = graphql.MarshalString(obj.ID)
		case "state":
            out.Values[i] = graphql.MarshalString(obj.State)
		case "user":
            out.Values[i] = _MarshalUser(ec.resolvers.User.Owner(ctx, obj))
        }
    }
    return out
}

*note: This is a simplified example of the generated code from gqlgen 0.5.1. The real code handles concurrent execution and error bubbling.

We can statically generate all the field selection, binding and json marshalling. We don’t need a single line of reflection to execute a GraphQL query! Now the compiler can catch errors all the way through for us. It can see every codepath through the runtime and catch the majority of bugs. We get great stack traces when things break, and this lets us iterate quickly on features inside gqlgen and in our apps.

At about this point we converted one of our development apps over from graphql-go, the PR:

  • removed 600 lines of hand written, hard to read, error prone DSL
  • added 70 lines of schema
  • added 70 lines of type safe resolver code
  • added 1000 lines of generated code

Get involved

From Christopher Biscardis Going GraphQL talk at Gopherpalooza 2018

Fast forward 6 months and we’ve seen 619 commits from 31 different contributors into gqlgen, making it one of the most feature-complete GraphQL libraries for Go.  We’ve had gqlgen in production on 99designs for most of this year, and we’ve seen a really positive response from the Go/GraphQL community.

This is just beginning! Some of the big features landing soon include:

  • Better directive support via a plugin system — being able to annotate the schema with validation and build plugins that allow seamless integration with codegen based ORMs like Prisma or XO.
  • Schema stitching — joining together multiple GraphQL servers, to expose a single, consistent org-wide view.
  • Schema-based gRPC/Twirp/Thrift bindings — being able to bind external services into your graph as easy as @grpc(service:”http://service”, method:”Foobar”)

We think gqlgen is the best way to build a GraphQL server in Go and possibly even any language. We’ve shipped a bunch of features so far, with many more to come and hope you’ll join us on GitHub or Gitter and join the adventure.

This post was written in collaboration with Luke Cawood and Mathew Byrne.