Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Getting Started With GraphQL Using Golang

TwitterFacebookRedditLinkedInHacker News

I’ve been hearing increasing amounts of buzz around GraphQL, a technology that has been around for quite a few years now. In case you’re not familiar, it is a technology for querying API data from a client-front end without having to make numerous requests or receiving unimportant data, both of which may cause negative affects on network latency.

Think of trying to query a relational database. Ideally you write a SQL query for the data you want and you do it in a single request. GraphQL tries to accomplish the same, but from an API consumption level.

We’re going to see how to implement a web application using the Go programming language, but uses GraphQL when working with the data.

Going into this, note that we’re going to be using mock data, not dynamic data residing in a database. If you want to see how to use GraphQL with Golang and a NoSQL database, check out my previous tutorial on the subject.

Bootstrapping a Golang Application in Preparation for GraphQL Support

We’re going to assume that you have Go installed and configured. You don’t need to be a Go wizard to be successful with this tutorial, but it probably shouldn’t be your first time using it.

From the command line, execute the following to install our GraphQL project dependency:

go get github.com/graphql-go/graphql

More information about our dependency can be found on the project’s GitHub page.

Create a project somewhere in your $GOPATH and inside that project, create a file called main.go which will hold all of our application code. Within the main.go file, include the following code as a starting point:

package main

import (
	"encoding/json"
	"net/http"

	"github.com/graphql-go/graphql"
)

type Album struct {
	ID     string `json:"id,omitempty"`
	Artist string `json:"artist"`
	Title  string `json:"title"`
	Year   string `json:"year"`
	Genre  string `json:"genre"`
	Type   string `json:"type"`
}

type Artist struct {
	ID   string `json:"id,omitempty"`
	Name string `json:"name"`
	Type string `json:"type"`
}

type Song struct {
	ID       string `json:"id,omitempty"`
	Album    string `json:"album"`
	Title    string `json:"title"`
	Duration string `json:"duration"`
	Type     string `json:"type"`
}

func main() {
	schema, _ := graphql.NewSchema(graphql.SchemaConfig{})
	http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
		result := graphql.Do(graphql.Params{
			Schema:        schema,
			RequestString: r.URL.Query().Get("query"),
		})
		json.NewEncoder(w).Encode(result)
	})
	http.ListenAndServe(":12345", nil)
}

In the above code we’re creating several Go data structures that will represent our data model in Go. We will also be creating a GraphQL data model, but it is important not to confuse the two as they are different. You’ll need both.

This particular application will be similar to iTunes. Let’s call it MyTunes. We’ll have information about albums, the songs that reside in them and the artists that perform them.

In the main function, we create a GraphQL schema, but for now we’re going to leave it without any information. Even though we’re not creating a RESTful API, that doesn’t mean we won’t need an API endpoint. With GraphQL we can send any and all queries to this single API endpoint and it will work its magic. The /graphql endpoint will accept requests that look like this:

curl -g 'http://localhost:12345/graphql?query={song{title,duration}}'

The important part of every request is the query property in the query parameters. This represents the GraphQL query that we’re sending to the server.

Before we get down and dirty with GraphQL, let’s come up with our mock data. Using a database is not the focus of this tutorial, so we don’t want to get hung up on those details.

Outside your main function, create the following:

var albums []Album = []Album{
	Album{
		ID:     "ts-fearless",
		Artist: "1",
		Title:  "Fearless",
		Year:   "2008",
		Type:   "album",
	},
}

var artists []Artist = []Artist{
	Artist{
		ID:   "1",
		Name: "Taylor Swift",
		Type: "artist",
	},
}

var songs []Song = []Song{
	Song{
		ID:       "1",
		Album:    "ts-fearless",
		Title:    "Fearless",
		Duration: "4:01",
		Type:     "song",
	},
	Song{
		ID:       "2",
		Album:    "ts-fearless",
		Title:    "Fifteen",
		Duration: "4:54",
		Type:     "song",
	},
}

We’ll keep it simple for now, but we’ve got two songs that are part of the Fearless album by Taylor Swift. At this point in time, we can do some preparation for querying with a GraphQL query.

Querying for Data with a GraphQL Query

We have the foundation of our application in place as well as a set of data to work with. Let’s try to query for that data, eventually with cURL or some other front-end application.

Remember when I mentioned the Go data structures were not our GraphQL data structures? Let’s create our GraphQL data structures.

In the main function of our application, add the following:

songType := graphql.NewObject(graphql.ObjectConfig{
	Name: "Song",
	Fields: graphql.Fields{
		"id": &graphql.Field{
			Type: graphql.String,
		},
		"album": &graphql.Field{
			Type: graphql.String,
		},
		"title": &graphql.Field{
			Type: graphql.String,
		},
		"duration": &graphql.Field{
			Type: graphql.String,
		},
	},
})

We can create a GraphQL object and define the fields or properties that exist in that object. Each field needs to have a type, where as in this case they are all strings. The same needs to apply for each of our models.

Take the GraphQL model for artists:

artistType := graphql.NewObject(graphql.ObjectConfig{
    Name: "Artist",
    Fields: graphql.Fields{
        "id": &graphql.Field{
            Type: graphql.String,
        },
        "name": &graphql.Field{
            Type: graphql.String,
        },
        "type": &graphql.Field{
            Type: graphql.String,
        },
    },
})

Finally, we can take a look at the model for each of our albums:

albumType := graphql.NewObject(graphql.ObjectConfig{
    Name: "Album",
    Fields: graphql.Fields{
        "id": &graphql.Field{
            Type: graphql.String,
        },
        "artist": &graphql.Field{
            Type: graphql.String,
        },
        "title": &graphql.Field{
            Type: graphql.String,
        },
        "year": &graphql.Field{
            Type: graphql.String,
        },
        "genre": &graphql.Field{
            Type: graphql.String,
        },
        "type": &graphql.Field{
            Type: graphql.String,
        },
    },
})

Yes, each of our fields in all three models are strings, but it isn’t really any different if you needed to change it to something else. Each of the GraphQL models were designed around the Go data structures. We can’t really work with the GraphQL models in Go like we can a native Go data structure.

The next GraphQL object we create will have a similar form, but will represent each of the possible queries we can perform. Having a model is meaningless unless we can apply it to some logic.

Take the rootQuery object, which has no specific or required name:

rootQuery := graphql.NewObject(graphql.ObjectConfig{
	Name: "Query",
	Fields: graphql.Fields{
		"songs": &graphql.Field{
			Type: graphql.NewList(songType),
			Resolve: func(params graphql.ResolveParams) (interface{}, error) {
				return nil, nil
			},
		},
	},
})

Every field that exists in this object will represent a possible query. We define a return type, in this case a list of songType and we provide a Resolve function that will be responsible for all of our driving logic.

When querying songs, we might want something like this for a Resolve function:

"songs": &graphql.Field{
	Type: graphql.NewList(songType),
	Resolve: func(p graphql.ResolveParams) (interface{}, error) {
		return songs, nil
	},
},

We’re returning the mock songs data. When we hook it up, and we will later, we could execute a query like this:

{
    songs {
        title,
        duration
    }
}

Notice that I’ve left out the id as well as the album property? That is because with GraphQL, you get to define exactly the data you want returned to the client.

Alright, that wasn’t a very good example because we’re just returning a variable and not really doing any logic. How about we modify it to be like the following:

func Filter(songs []Song, f func(Song) bool) []Song {
	vsf := make([]Song, 0)
	for _, v := range songs {
		if f(v) {
			vsf = append(vsf, v)
		}
	}
	return vsf
}

func main() {
    // ...
    "songs": &graphql.Field{
        Type: graphql.NewList(songType),
        Args: graphql.FieldConfigArgument{
            "album": &graphql.ArgumentConfig{
                Type: graphql.NewNonNull(graphql.String),
            },
        },
        Resolve: func(params graphql.ResolveParams) (interface{}, error) {
            album := params.Args["album"].(string)
            filtered := Filter(songs, func(v Song) bool {
                return strings.Contains(v.Album, album)
            })
            return filtered, nil
        },
    },
    // ...
}

Don’t pay too much attention to the Filter function. What matters here is that we’ve included Args to the field which let’s us define which parameters are allowed to be passed in. In the Resolve function we’re getting the album parameter and filtering our slice for only songs of that album. Things would be different with a database, but use your imagination.

The actual query for GraphQL would look like this:

{
    songs(album: "ts-fearless") {
        title,
        duration
    }
}

Getting the hang of things now?

Alright, we’ve got a way to query for songs by album, so let’s create a field for querying album information as well as song information.

First we should create a query field for any particular album:

"album": &graphql.Field{
	Type: albumType,
	Args: graphql.FieldConfigArgument{
		"id": &graphql.ArgumentConfig{
			Type: graphql.NewNonNull(graphql.String),
		},
	},
	Resolve: func(params graphql.ResolveParams) (interface{}, error) {
		id := params.Args["id"].(string)
		for _, album := range albums {
			if album.ID == id {
				return album, nil
			}
		}
		return nil, nil
	},
},

In the above example we’re expecting a parameter and we’re returning a single albumType result rather than a list. If we wanted to craft a fancy query, we could do something like this:

{
	album(id: "ts-fearless") {
		title
	}
	songs(album: "ts-fearless") {
		title
	}
}

The above GraphQL query would get a single album by the id as well as all songs for the same particular album. In the result we’re only asking for the titles of both, but we could easily change that.

We haven’t adding querying to our GraphQL schema, so up until now it won’t function. It is an easy fix though. Head back into the schema variable we had created earlier and do the following:

schema, _ := graphql.NewSchema(graphql.SchemaConfig{
	Query: rootQuery,
})

We’ve got querying down as of now. We could make things more complicated, but the strategy won’t really change. Now we can focus on altering data.

Create, Delete, and Update Data by using GraphQL for Mutations

GraphQL supports mutations as well as read-only queries. Both are no more difficult than the other and setup is more or less the same, which is convenient for us.

We’re going to create a new GraphQL object like we did for our queries. This object will hold all of our mutation queries. Start it off by doing the following:

rootMutation := graphql.NewObject(graphql.ObjectConfig{
	Name: "Mutation",
	Fields: graphql.Fields{},
})

The naming doesn’t really matter. We’re going to hook it up to our schema like we did with the rootQuery object. Let’s create a mutation that will allow us to create a new song:

"createSong": &graphql.Field{
	Type: songType,
	Args: graphql.FieldConfigArgument{
		"id": &graphql.ArgumentConfig{
			Type: graphql.NewNonNull(graphql.String),
		},
		"album": &graphql.ArgumentConfig{
			Type: graphql.NewNonNull(graphql.String),
		},
		"title": &graphql.ArgumentConfig{
			Type: graphql.NewNonNull(graphql.String),
		},
		"duration": &graphql.ArgumentConfig{
			Type: graphql.NewNonNull(graphql.String),
		},
	},
	Resolve: func(params graphql.ResolveParams) (interface{}, error) {
		var song Song
		song.ID = params.Args["id"].(string)
		song.Album = params.Args["album"].(string)
		song.Title = params.Args["title"].(string)
		song.Duration = params.Args["duration"].(string)
		songs = append(songs, song)
		return song, nil
	},
},

In the above example, we have a createSong field that returns a songType object. For input parameters, we’re expecting everything that composes our object. For the Resolve function, we are taking the data and adding it to the mock list of data.

Let’s wire it up to our schema:

schema, _ := graphql.NewSchema(graphql.SchemaConfig{
	Query:    rootQuery,
	Mutation: rootMutation,
})

That wasn’t difficult was it?

Running a query that contains a mutation is really no more difficult than running a query that is read only. Take the following for example:

mutation {
	createSong(id: "7", album: "ts-fearless", title: "Breathe", duration: "4:23") {
		title,
		duration
	}
}

You’ll notice that this particular query requires that we define it as a mutation. If we wanted to execute this query via a cURL command, it would look like the following:

curl -g 'http://localhost:12345/graphql?query=mutation+_{createSong(id:"7",album:"ts-fearless",title:"Breathe",duration:"4:23"){title,duration}}'

Not too bad right?

We could go ahead and create more mutations, but the process would be more or less the same, just as we saw with the other queries. There isn’t too much overhead with GraphQL.

Conclusion

You just saw how to get started with GraphQL in a Golang application. We didn’t use a database for this example, but we saw how to create a data model, query it, and manipulate data via some kind of GraphQL mutation.

If you’re interested in wiring a NoSQL database to your GraphQL with Golang application, check out a previous tutorial that I wrote titled, Using GraphQL with Golang and a NoSQL Database.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.