[edit]

Getting Started

Building graphql servers in golang

Goal

The aim for this tutorial is to build a “todo” graphql server that can:

You can find the finished code for this tutorial here

Install gqlgen

Assuming you already have a working go environment you can simply go get:

go get -u github.com/99designs/gqlgen github.com/vektah/gorunpkg

Building the server

Define the schema

gqlgen is a schema-first library, so before touching any code we write out the API we want using the graphql Schema Definition Language. This usually goes into a file called schema.graphql

schema.graphql

type Todo {
  id: ID!
  text: String!
  done: Boolean!
  user: User!
}

type User {
  id: ID!
  name: String!
}

type Query {
  todos: [Todo!]!
}

input NewTodo {
  text: String!
  userId: String!
}

type Mutation {
  createTodo(input: NewTodo!): Todo!
}

Create the project skeleton

$ gqlgen init
Exec "go run ./server/server.go" to start GraphQL server

This has created an empty skeleton with all files we need:

Create the database models

The generated model for Todo isnt quite right, it has a user embeded in it but we only want to fetch it if the user actually requested it. So lets make our own.

todo.go

package gettingstarted

type Todo struct {
	ID     string
	Text   string
	Done   bool
	UserID string
}

And then tell gqlgen to use this new struct by adding this to the gqlgen.yml:

models:
  Todo:
    model: github.com/vektah/gqlgen-tutorials/gettingstarted.Todo

and regenerate by running

$ gqlgen -v
Unable to bind Todo.user to github.com/vektah/gqlgen-tutorials/gettingstarted.Todo
	no method named user
	no field named user
	Adding resolver method

note we’ve used the verbose flag here to show what gqlgen is doing. Its looked at all the fields on our model and found matching methods for all of them, except user. For user it added a resolver to the interface we need to implement. This is the magic that makes gqlgen work so well.

Implement the resolvers

The generated runtime has defined an interface for all the missing resolvers that we need to provide. Lets take a look in generated.go

// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface.
func NewExecutableSchema(cfg Config) graphql.ExecutableSchema {
	return &executableSchema{
		resolvers:  cfg.Resolvers,
		directives: cfg.Directives,
	}
}

type Config struct {
	Resolvers  ResolverRoot
	Directives DirectiveRoot
}

type ResolverRoot interface {
	Mutation() MutationResolver
	Query() QueryResolver
	Todo() TodoResolver
}

type DirectiveRoot struct {
}
type MutationResolver interface {
	CreateTodo(ctx context.Context, input NewTodo) (Todo, error)
}
type QueryResolver interface {
	Todos(ctx context.Context) ([]Todo, error)
}
type TodoResolver interface {
	User(ctx context.Context, obj *Todo) (User, error)
}

Notice the TodoResolver.User method? Thats gqlgen saying “I dont know how to get a User from a Todo, you tell me.”. Its worked out how to build everything else for us.

For any missing models (like NewTodo) gqlgen will generate a go struct. This is usually only used for input types and one-off return values. Most of the time your types will be coming from the database, or an API client so binding is better than generating.

Write the resolvers

This is a work in progress, we have a way to generate resolver stubs, but it only cant currently update existing code. We can force it to run again by deleting resolvers.go and re-running gqlgen:

rm resolvers.go
gqlgen

Now we just need to fill in the not implemented parts

graph/graph.go

//go:generate gorunpkg github.com/99designs/gqlgen

package gettingstarted

import (
	context "context"
	"fmt"
	"math/rand"
)

type Resolver struct{
	todos []Todo
}

func (r *Resolver) Mutation() MutationResolver {
	return &mutationResolver{r}
}
func (r *Resolver) Query() QueryResolver {
	return &queryResolver{r}
}
func (r *Resolver) Todo() TodoResolver {
	return &todoResolver{r}
}

type mutationResolver struct{ *Resolver }

func (r *mutationResolver) CreateTodo(ctx context.Context, input NewTodo) (Todo, error) {
	todo := Todo{
		Text:   input.Text,
		ID:     fmt.Sprintf("T%d", rand.Int()),
		UserID: input.UserID,
	}
	r.todos = append(r.todos, todo)
	return todo, nil
}

type queryResolver struct{ *Resolver }

func (r *queryResolver) Todos(ctx context.Context) ([]Todo, error) {
	return r.todos, nil
}

type todoResolver struct{ *Resolver }

func (r *todoResolver) User(ctx context.Context, obj *Todo) (User, error) {
	return User{ID: obj.UserID, Name: "user " + obj.UserID}, nil
}

We now have a working server, to start it:

go run server/server.go

then open http://localhost:8080 in a browser. here are some queries to try:

mutation createTodo {
  createTodo(input:{text:"todo", userId:"1"}) {
    user {
      id
    }
    text
    done
  }
}

query findTodos {
  	todos {
      text
      done
      user {
        name
      }
    }
}

Finishing touches

gqlgen is still unstable, and the APIs may change at any time. To prevent changes from ruining your day make sure to lock your dependencies:

Note: If you dont have dep installed yet, you can get it here

First uninstall the global version we grabbed earlier. This is a good way to prevent version mismatch footguns.

rm ~/go/bin/gqlgen
rm -rf ~/go/src/github.com/99designs/gqlgen

Next install gorunpkg, its kind of like npx but only searches vendor.

dep init
dep ensure

At the top of our resolvers.go a go generate command was added that looks like this:

//go:generate gorunpkg github.com/99designs/gqlgen

This magic comment tells go generate what command to run when we want to regenerate our code. to do so run:

go generate ./...

gorunpkg will build and run the version of gqlgen we just installed into vendor with dep. This makes sure that everyone working on your project generates code the same way regardless which binaries are installed in their gopath.