2

Building a GraphQL Example Application with Golang [Tutorial]

 3 years ago
source link: https://www.inovex.de/blog/graphql-application-golang-tutorial/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Gabriel-Mihai Ruiu

Most of the people reading this are probably familiar with using and implementing REST interfaces to manage data exchange between automated processes. It is a tried and tested option which has more than ample support in many programming languages. Not only that but it has been around long enough to teach us a few lessons on what we need to look out for when we try to implement data-exchange interfaces that should be easy to maintain and evolve over time. Some of the lessons are encompassed in a new type of API language called GraphQL , which was initially developed by Facebook and later gained much popularity and support from the open-source community.

There’s quite a bit to unpack when talking about GraphQL but I will only underline some of the major features since it’s not the scope of this article to go in-depth:

  • type safety; this means that there is also integrated validation for incoming and outgoing data
  • integrated schema introspection; that means you get the luxury of checking out what the API can do without needing any special software, you just open up the GraphQL Playground and read through (it can also contain documentation); this of course has the added advantage of being able to automatically generate consumer clients
  • selective data retrieval; you limit the amount of query data to only what you need
  • integrated realtime updates via subscriptions

In order to get you familiar with some of the basics, what we are going to do is to try and get an example Todo application running, written in Golang using the 99designs/gqlgen library/framework. We’re going to extend the API bit by bit to cover some basics of getting data exposed through GraphQL.

NOTE: in this example I used go 1.15.

Why 99designs/gqlgen and not graphql-go/graphql?

At the time of writing this article the 99designs/gqlgen library has around 5k Github stars vs 6.9k stars for graphql-go/graphql, and the latter is most likely the first link you’ll find when you Google for “go graphql”. While the number of Github stars is only a loose indication of how popular a library is, in this case both of them have a fairly similar amount.

Despite that, I find 99designs/gqlgen to be the better option as it brings 3 great features in comparison to graphql-go/graphql, which are all listed in the very first paragraphs of the README file:

  • you can define your schema directly in *.graphqls definition files
  • the schema bindings are generated from the *.graphqls definition files, you only need to provide your business logic; no manual bootstrapping required
  • for each type defined in *.graphqls files a corresponding Go type is generated; that means we have the benefit of type safety (NOTE: you can also map to Go types written by yourself if you need more control)

In contrast, with graphql-go/graphql 

  • the schema is only programmatically defined which, from personal experience, can quickly become very hard to understand even with smaller schemas; just take a look at the star wars example
  • you often use map[string]interface{} or interface{} for type definitions, which is very vulnerable to human-error and potential bugs

What’s cool is that 99designs/gqlgen also delivers the GraphQL Playground by default. For graphql-go/graphql you need an extra library. One last point that I do find important is that 99designs/gqlgen has a significantly higher number of contributors (154 vs 83 from graphql-go/graphql).

Getting Started

Getting a minimal example going is fairly easy. Just create a project directory and run the following commands inside it.

go mod init graphql-go-example
go get github.com/99designs/gqlgen #current version is 0.13.0
go run github.com/99designs/gqlgen init

This creates the following tree structure, which encompasses a simple todo-management application:

$ tree -L 3 .
├── go.mod
├── go.sum
├── gqlgen.yml
├── graph
│ ├── generated
│ │ └── generated.go
│ ├── model
│ │ └── models_gen.go
│ ├── resolver.go
│ ├── schema.graphqls
│ └── schema.resolvers.go
└── server.go

gqlgen stores configuration in gqlgen.yml and uses it to customize the generation of the schema bindings. It’s worth taking a look through it to understand what aspects of the schema binding generation can be changed, but I’ll point out the following settings:

schema:
- graph/*.graphqls
exec:
filename: graph/generated/generated.go
package: generated
model:
filename: graph/model/models_gen.go
package: model

From this we get that files with the *.graphqls extension are interpreted as schema files and used to generate the bindings and types under graph/.

  • graph/generated/generated.go is where all the “glue” between the graphql specification and go is generated; you’ll hopefully never need to look inside
  • graph/model/models_gen.go is where the unmanaged types and fields are generated
  • graph/resolver.go is where we will add our dependencies, which will be shared between the different types of resolvers
  • graph/schema.resolvers.go contains the go receivers for the resolvers which correspond to the queries and mutations defined in schema.graphqls

Returning Some Todos in the GraphQL Interface

The default-generated go receivers for the query- and mutationResolver in schema.resolvers.go are just placeholders and throw panics. Let’s replace that with a simple in-memory list management:

graph/resolver.go

//we add a constructor method that instantiates the Todo and User slice 
func NewResolver() *Resolver {
  users := make([]*model.User, 0)
  users = append(users, &model.User{ID: "1", Name: "fphilip"})
  users = append(users, &model.User{ID: "2", Name: "lturanga"})
  return &Resolver{
    todos: make([]*model.Todo, 0),
    users: users,
type Resolver struct {
  todos      []*model.Todo
  users      []*model.User
  lastTodoId int

server.go

//instead of ‘generated.Config{Resolvers: &graph.Resolver{}})’
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: graph.NewResolver()}))

graph/schema.resolvers.go

func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
  var targetUser *model.User
  for _, user := range r.users {
    if user.ID == input.UserID {
      targetUser = user
      break
  if targetUser == nil {
    return nil, fmt.Errorf("user with id='%s' not found", input.UserID)
  newTodo := &model.Todo{
    ID:   strconv.Itoa(r.lastTodoId),
    Text: input.Text,
    Done: false,
    User: targetUser,
  r.todos = append(r.todos, newTodo)
  r.lastTodoId++
  return newTodo, nil
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
  return r.todos, nil

Now we’ve also got a functional example. Start the application using

go run server.go

and access http://localhost:8080 . You’ll get the embedded GraphQL Playground which has some nice features like automatic schema introspection and autofill.

Quick intro to the GraphQL Playground: in the left panel you write the requests and on the right side you get the response. You can also use tabs.

Let’s get the current list of todos by executing the query:

  todos {
    user {

For that we get, as expected, an empty list

  "data": {
    "todos": []

Let’s not leave it at that and add some entries using the createTodo mutation.

mutation {
  createTodo(input:{text:"get flowers for Leela", userId:"1"}) {
    user {

The entry gets a new id which we can see in the response

  "data": {
    "createTodo": {
      "id": "0",
      "text": "get flowers for Leela",
      "done": false,
      "user": {
        "id": "1",
        "name": "fphilip"

Add New Query and Mutation

We can add a new todo entity and we can list all existing todos. But this was with the generated query and mutation from the gqlgen init process we executed in the beginning. Let’s make our own, this time for user entities.

The first thing is to adapt the GraphQL schema:

type Query {
  todos: [Todo!]!
  users: [User!]!
input NewUser {
  name: String!
type Mutation {
  createTodo(input: NewTodo!): Todo!
  createUser(input: NewUser!): User!

We added a new users query and a createUser mutation, which needs a corresponding input object.

This, by itself, is not enough yet, since we need to regenerate the go bindings based off of our new schema, which is done by executing:

go run github.com/99designs/gqlgen generate

gqlgen then takes care of putting all the components together so we can extend our functionality. The new model which corresponds to the NewUser input is generated with the same name and the methods corresponding to the new query and mutation are in schema.resolvers.go:

func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {
  panic(fmt.Errorf("not implemented"))
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
  panic(fmt.Errorf("not implemented"))

Everything else we wrote before in schema.resolvers.go stays untouched. That’s great!

So now let’s update the code to perform the same kind of management as with the todos.

resolver.go

func NewResolver() *Resolver {
  users := make([]*model.User, 0)
  users = append(users, &model.User{ID: "1", Name: "fphilip"})
  users = append(users, &model.User{ID: "2", Name: "lturanga"})
  return &Resolver{
    todos: make([]*model.Todo, 0),
    users: users,
    lastUserId: 3,
type Resolver struct {
  todos      []*model.Todo
  users      []*model.User
  lastTodoId int
  lastUserId int

schema.resolvers.go

func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {
  newUser := &model.User{
    ID:   strconv.Itoa(r.lastUserId),
    Name: input.Name,
  r.users = append(r.users, newUser)
  r.lastUserId++
  return newUser, nil
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
  return r.users, nil

Now let’s start our server again and see what we get when we add a new user using the Playground:

//request
mutation {
  createUser(input:{name:"brodriguez"}) {
//response
  "data": {
    "createUser": {
      "id": "3",
      "name": "brodriguez"

Yay, this means we get the new entry also when querying all users:

//request
  users {
//response
  "data": {
    "users": [
        "id": "1",
        "name": "fphilip"
        "id": "2",
        "name": "lturanga"
        "id": "3",
        "name": "brodriguez"

So let’s break it down:

  • we started by adapting the schema file
  • the go bindings were regenerated using gqlgen which takes care of not disturbing anything you already wrote
  • we adapted the new methods corresponding to the new query and mutation

Adding a Subscription

If you thought what we did until now was easy, then you’ll be surprised how much easier it is to add support for subscriptions. Let’s say we wanted to offer the consumers of the GraphQL interface the possibility of seeing in realtime which users are added.

schema.graphqls

type Subscription {
  userAdded: User!

We regenerate the go bindings, just like before, and gqlgen creates a whole new resolver type dedicated just for subscriptions, with a single receiver called UserAdded:

func (r *subscriptionResolver) UserAdded(ctx context.Context) (<-chan *model.User, error) {
  panic(fmt.Errorf("not implemented"))

The first thing to notice is that the return type for the receiver method is a go channel. That’s because the framework is asking for the channel on which it will asynchronously start listening to for events where the payload is an instance of *model.User. Each instance sent on this channel will be distributed to all active subscription clients.

func (r *subscriptionResolver) UserAdded(ctx context.Context) (<-chan *model.User, error) {
  return r.usersChan, nil

Of course we also have to instantiate the channel:

resolver.go

func NewResolver() *Resolver {
  return &Resolver{
    usersChan: make(chan *model.User),
type Resolver struct {
  usersChan chan *model.User

The only thing left to do is to take care that, when a new user is added, we send the instance on the channel and the framework will take care of the rest.

schema.resolvers.go

func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {
  r.usersChan <- newUser
  return newUser, nil

To see this in action, we go back to the Playground and start the subscription, which creates the connection in the background :

subscription {
  userAdded {

Now anytime someone calls the createUser mutation, the active subscription connection will get a copy of that new user.

Creating Custom Resolvers for Individual Fields

So far gqlgen has resolved the value of the individual entity fields by making a 1:1 mapping with the fields with the same name from the generated Go structs. i.e. if we ask for the value of Todo.id entity, we’ll get the value from the Todo.ID go struct.

During development however we may come across use cases where we find that it is better to lazy-load some of the data which can be retrieved from an entity. After all, keep in mind that a consumer of the GraphQL interface can decide to only retrieve a part of the entity data.

Let’s say we need to also deliver the length of the todos text.

schema.graphqls

type Todo {
  textLength: Int!

If we regenerate the bindings with just these schema changes, the framework will extend the Todo go struct with the textLength field, which we will need to populate every time an instance of the entity is delivered. But for the sake of this example let’s say that the operation to determine the text length is CPU intensive enough to warrant a lazy-loading approach. For that, we need to tell the framework we want to compute the value only if the data is explicitly asked for. In technical terms, this translates to configuring the value of the textLength field to be computed in a dedicated resolver.

gqlgen.yml

models:
  Todo:
    fields:
      textLength:
        resolver: true

Regenerating the go bindings at this point gives us a new receiver which will do the “heavy-lifting” of computing the length of text from a todo:

func (r *todoResolver) TextLength(ctx context.Context, obj *model.Todo) (int, error) {
  return len(obj.Text), nil

But lazy-loading is of course not the only use case. It can also be used for caching results, retry mechanisms and anything that might fit your use-case.

Conclusion

We’ve covered some solid basics of implementing a GraphQL interface using golang. There are other topics that can be explored but the intention of the article is to keep it scoped to beginners. What was not covered in this article are some features around type definitions in GraphQL like scalars, unions, interfaces, fragments etc. but they are very well explained in the official documentation. Make sure to check out the documentation from gqlgen to see if there are differences between the official specification and what the library supports.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK