Intro 101: GraphQL in Go

Posted by : at

Category : go   graphql


In this article we will be building a hiring agency API for gophers, using a GraphQL server built in Go. Unlike many other introductions out there I won’t be listing the name of all components with a short declaration. Instead, we will build an application step by step and explain what is going on in each step. Hopefully, this approach makes it a bit easier to not flood you with information. I wont only cover GraphQL, but also how I structure the architecture when using graphql-go

The first thing we need to clear out is a very common misconception.

GraphQL is not a database! - GraphQL

I think this misunderstanding comes from the name, developers are used to hearing all the database names MySQL, PostgreSQL, etc. What is important is the last two letters, QL. If you’ve been around for some time, you might know this is short for Query Language but many I’ve met just relates it to databases.

So, GraphQL is a Query language built to make communicating between servers and clients easier and more reliant. One other thing we need to sort out right away is, GraphQL is database agnostic. This means that it does not care what underlying database is being used. 

All images in this article are drawn by Percy Bolmér, the Gopher is drawn by Takuya Ueda, inspired by the works of Renée French. The gopher has been modified in the images.

How is that possible you ask? Well, you should consider GraphQL as a middleware between your database and client. You have a GraphQL server that receives requests in the Query format, the query specifies exactly what data you want to be returned, the server returns the data defined in the request.  The query language is defined and documented on the graphql website.

This is how a query could look when searching for gophers This is how the response would look for the example query, notice that only the wanted fields are present

If you have any further questions about GraphQL, they have a very nice FAQ.

In this article we will cover the basics, we will be building an application for searching and finding gophers to hire. We won’t cover all aspects of GraphQL, that would be too long.

Why and when you should use GraphQL

GraphQL - Using multiple data sources but one response and one request

When I first heard about GraphQL, I admit, I was skeptical. And I’ve understood many others are as well. It sounds like just adding another layer between the API and the caller will make things more complex. 

But this is not the case, following along and I hope to convince you of that.

One of the best reasons I find for using GraphQL is the ability to combine data sources. I am a big user of the RepositoryPattern which you can find discussed in my Domain-Driven Design article. 

In short, you have a repository for each data source. For the agency we are building we will have GopherRepository which stores gophers, and a JobRepository that stores jobs. GraphQL makes it possible to easily combine these two structures into a single output, without coupling the data sources in the backend. In the server we will build, it will look like the Gopher struct has Jobs related to it, but it will actually be two different storage solutions.

The second reason I like GraphQL is the ability to avoid overfetching by allowing the user to specify exactly what fields to request. You only send one request and get only the information you request, no extra fields that are unused.

One of the principles in GraphQL is that the development process should start with the Schema definition. This is called Schema Driven Development and I won’t cover it here, but basically, we start with the schemas instead of the business logic.

In GraphQL your API starts with a schema that defines all your types, queries and mutations, It helps others to understand your API. So it’s like a contract between server and the client. - GraphQL Website

How to use GraphQL in Go

Downloading the required packages and creating a go module

The first thing to do is to decide on what Go package to use. The GraphQL website maintains a list of all available packages

In this article, we will be using graphql-go/graphql which is a package that is built after the official graphql-js reference. This package does not read graphql schema files, rather we define the schemas in the go code. And the way to define the schemas matches the same way used in the javascript side, so that is nice. 

We will begin by creating a package and getting the required libraries to get started. We will be fetching graphql-go/graphql which is used for building and defining our schemas, and graphql-go/handler which is used to host the graphql server. 

go mod init github.com/programmingpercy/gopheragency
go get github.com/graphql-go/graphql
go get github.com/graphql-go/handler
touch main.go

Queries and Resolvers - Fetching data

Queries are the requests for data, Resolvers handles the business logic to find the data

The query is the operation to fetch data from the server. A query is performed by sending a request according to the Schema declaration. You saw an example query earlier in the article, and we will use that as a starting point. Only the fields used in a query will be returned in the response. 

There are two keywords we need to learn before moving on.  

  • Field - A value of a certain data type, String, Int, Float, Boolean, and ID
  • Object - An object with fields , think of it like a struct.

Example where we show what each thing in a Query is

To begin implementing this we need to start building the schema, remember Schema Driven Development, right? 

We will begin by creating the Gopher object that we can query. Create a schema folder and a gopher.go inside of it.   We will begin by creating the GopherType which is a declaration of an object that can be sent using GraphQL. To create a new object in graphql-go/graphql we use the graphql.NewObject function, the input to NewObject is an ObjectConfig.

This ObjectConfig is a struct that is used to configure how the object is defined. The config holds a graphql.Fields object which is an alias for map[string]*graphql.Fields, remember that Objects are a set of Fields.

schemas/gopher.go - A declaration of the Gopher Object type

Now that we have defined the GopherType we have to set up and host a GraphQL server that can respond to queries. 

To have a GraphQL server we need a RootQuery which is the base for each query. The root query will hold all available queries at the top level. 

When a request reaches the server, data has to be fetched. Fetching data is done by Resolvers which is a function that accepts the query and all the arguments. We will create a simple server that responds with a simple Hello first before adding the GopherType.

main.go - A simple graphQL Server that exposes on port :8080

After you’ve updated the main.go to host the server on port 8080 then visit localhost and you’ll see a UI where we can test the current implementation. 

On the left side you can write your Query, in the middle you see the response from the Resolver, and to the right you see a clickable tree structure that you can use to see all available queries.

GraphiQL - A website to test and experiment with Queries

Try it out to see if you get the correct response to make sure everything is working. 

Now it’s time to implement an actual resolver, we will create a new folder named gopher which will contain a Resolver used to fetch Gophers. Create the resolver.go file which will define all the Resolver functions we expect from data storage for Gophers. 

Remember, any function that looks like funcName(p graphql.ResolveParams) (interface{},error)` can be used as a resolver. 

gopher/resolver.go - The resolver interface that defines our Gopher Resolver

Now that we have defined a resolver interface, we need to implement it. Let’s use in memory data storage that contains a few gophers. The Gopher struct must have the JSON tags to match the defined GopherType. If the tags don’t match then the response won’t be returned.

Create a gopher.go and fill in the struct. 

gopher/gopher.go - The gopher struct that matches the GopherType Object

Let’s create a Repository that defines the functions needed to act as Gopher storage. Create repository.go and insert the following gist.

gopher/repository.go - A repository interface that defines a Gopher storage Next, we implement a simple in-memory data repository. We will create a memory.go and fill in the super simple storage solution that generates two gophers for us. 

memory.go - The simple storage solution for in memory gophers

Great, we can now start implementing the Resolvers that are used to handle the queries. Let’s begin simple and implement the response with all Gophers available. We will have a struct that fulfills the Resolver interface. The reason why we have a structure this way will become more clear later, but a Resolver can hold many Repositories to combine data. 

resolver.go - Added a ResolveGophers function that can be used as a Resolver in the servers root query To start using the ResolveGophers from the GopherService we need to create a service in main.go and also make the RootQuery return a List of GopherType instead. Remember GopherType was the custom Object we created earlier, and a List is an Array in GraphQL.

main.go - GraphQL Root query updated to return a List of Gophers and to Resolve them using our InMemoryRepository Now restart the program go run main.go and visit localhost, time to see how GraphQL allows us to avoid over fetching and under fetching.

Remember that in GraphQL, we define what fields to return in the query, so only the fields you query will be returned. See the two images below, in I only fetch the Gophers names, the second image shows how to fetch all data available.

GraphiQL - Query to Fetch only the name for all Gophers

GraphiQL - Query to fetch all values available for Gophers

Combining Data in Queries without Coupling data sources

Combine objects without coupling them

Before we move on, It’s time to show how we can use multiple repositories to fetch data from many sources. This is another great feature with GraphQL.

Each graphql.Field has the Resolve field, so you can input a resolve function to each graphql.Field. Usually, we need access to a storage/repository in the resolver, the easiest way to achieve this is by using the Service to generate the Schema as it has all access needed.

Let’s see when we implement a JobRepository which will be used to handle Jobs. We will store both the JobRepository and the GopherRepository in the GopherService, and create a GenerateSchema function in the schema package that accepts the service as input and creates the schema we can use for the GraphQL. This approach allows us to build resolvers that have access to all data sources so we can combine them.   Begin by creating a job folder and create the job structure that we will use internally. We will also create a Repository for the job. 

job/job.go - The job struct used in the backend domain Next, we need a structure that is part of the repository, this time also an in-memory solution. 

job/memory.go - A in memory solution for a job Repository

The last thing to do before we start fixing the Resolvers is to upgrade the Service so it has access to a JobRepository

gopher/resolver.go - The gopher service now accepts a Job repository

Now it’s time to pay focus on what happens here. We will add a ResolveJobs function in this function we will access a Source field, this field is the Parent of the object. This is very useful when we want to use data from the query itself, like in this case when we search for a Job we need the ID of the Gopher.

The Source will be a Gopher object, so we need to typecast it. Then use the ID of that gopher to the jobRepository 

gopher/resolver.go - ResolveJobs will want the parent object to be a gopher

Time to start building the schema, this is where we can combine data from the GopherService.   Create the GraphQL object to represent the JobType in schemas/factory.go

schemas/factory.go - The GraphQL object to represent jobs

Let’s begin by fixing the Field for the Jobs array. Notice how we pass the service as a parameter so we can reach the needed Resolver function.

factory.go- building the jobs field which is a list of jobtypes

Now that we have the Job field done, we want to set that as an available data field for the Gopher type. We will remove the gopher.go file and move the content into its generator function, because again, we need the GopherService present. Remember that the Jobs field will be a child to Gopher, this makes the Source correct.

schemas/factory.go - Generating the GopherType, which contains a field for Jobs.

It’s time to finalize the RootQuery and the Schema in a GenerateSchema which is exported to other packages. 

factory.go - The GenerateSchema which chains together all the pieces into a RootQuery To implement, remove the old main function in main.go and use the newly created Repositories and Schema generator.

main.go - The even easier version with no domain knowledge for the main function

Restart the application and try querying for jobs. You should see that under each Gopher there is a list of jobs presented in the JSON response. Amazing right? 

JSON Response for the Gophers query

If you wonder about the job resolver and gopher resolver that we created, there is a reason why we haven’t implemented them yet. We don’t want to fetch ALL items each time, but we want to be able to query for certain Gophers only. Enter Arguments.

Arguments - Ability to specify search values

Specify certain data to query by using arguments

It makes sense to have a way of searching for specific data. This way is done by using Arguments in the Query. An Argument is a named value to include in the query. 

A GraphQL field can have zero or many arguments available. 

It’s easier to probably implement it to learn, so let’s add the ability to query specific Gophers and Jobs. 

In the graphql.ResolveParams we have an Args field that will contain all the arguments sent in the query. 

We can use the Args field to search for any Argument we add. Let’s add an argument to the Jobs Resolver first, in which we ask for a company argument.

Update the JobRepository to accept yet another parameter which is the companyName.

job/memory.go - GetJobs updated to get company name

We also fix the Repository to correlate the new changes.

job/job.go - Repository changes

Now let’s fix the ResolveJobs to check for the Argument. 

gopher/resolver.go - The Resolver now accepts argument Company

The last thing we need to do is make the graphql.Field also accepts the argument. Each graphql.Field has an Args that can be used to define the possible arguments, Args is a map[string]*ArgumentConfig in where we have to set the name and the data type. Let’s add it to the schemas/factory

schemas/factory - The jobs field now has an argument Now you can request certain companies by using an argument in the query. The arguments are added by using the following syntax after the Field.

jobs(company: "value"){

The image shows how we can filter for certain Jobs by using and Argument

Mutations - Modify the Data

Mutations in GraphQL allows changes to data

Great, we can query data from the server. What about when we want to modify data? 

That is when we have mutations in GraphQL. A mutation is defined by a RootMutation just as we had a RootQuery. So we need to build a schema for all available mutations, their arguments, and fields available.

A Mutation in GraphQL will look very much like a Query, and in fact, it will also return the result. So a Mutation can be used to apply new values, but also to fetch values after they are applied. Just as a Query you defined what values to return.   Let’s allow JobRepository to GetJob by an ID and to Update the job, this is so we can later create a Mutation to modify the start and end date of a job.

job/job.go - Update the repository with two new functions  

Then open the memory.go and update the storage solution to handle these new functions. 

job/memory.go - In memory solution to handle job updates

Next, we need to add a Resolver function to the GopherService, one thing I’ve noticed with graphql-go is that there is quite the overhead since we deal with a lot of interface{}. You can avoid much overhead by creating helper functions to deal with the type assertions, in the code snippet below you see a grabStringArgument which is used to extract GraphQL Arguments that are strings. The mutation resolver is just like the query resolver so there isn’t anything new here. 

gopher/resolver.go - Added a mutation resolver which updates a job.

Next, we need to update the Schema to have a mutation. Creating all the graphql.Fields can become quite much code as you might have noticed, so I usually create a generator function that reduces the code duplication by a factor of a hella lot.

To create a field we need a Type, which is anything that fulfills an Output interface which looks like the following snippet

type Output interface {
    Name() string
    Description() string
    String() string
    Error() error
}

The second parameter is the Resolver which is an alias FieldResolveFn func(p ResolveParams) (interface{},error), the third is a string description, and the fourth a map of arguments to allow. 

schemas/factory.go - The generic factory for graphQL fields

Time to build the mutation query, create a mutation.go file in the schemas package. We begin by creating the arguments available in the request. We want the mutation request to require two arguments, so we create them using graphql.NewNonNull function. Using NewNonNull will make the GraphQL server trigger an error if a request with empty values is sent.

schemas/mutation.go - The arguments available for the Mutatejob request.

We need to create the RootMutation with the new mutation in it, just as the query was created. We will now be using the generateGraphQLField to make the code a lot shorter. 

schemas/mutation.go - Building the root mutation

The last thing to do before we can try the mutation is to apply the RootMutation to the Schema in factory.go 

schemas/factory.go - Adding the root mutation to the Schema Config.

Open GraphiQL to try it out. We send the Mutation in the same way as the Query, simply replace the Keyword.

GraphiQL - Sending a Mutation request to change the start date

Conclusion

We have covered the very core of GraphQL. During the article you have learned how to Query data, Mutate it and the basics of how GraphQL works. You have seen how we can combine data sources to a single output and how to structure the project. 

GraphQL has grown a lot and has much more to offer. Here are some topics you should investigate by yourself to increase your knowledge. 

  • Enumerations
  • Interfaces, Fragments and Inline Fragments
  • Subscriptions

If you wish to learn more, I can recommend The Road To GraphQL by Robin Wieruch. The book is not focused on Go, but rather on GraphQL and from a frontend perspective. The book will cover how to use GraphQL in React with a Node backend.  

Full disclosure - The link to the book is an affiliate link, meaning I am entitled to some of the sales revenue. 

Will you be using GraphQL or not?

You can find the full code on GitHub.


About Programming Percy
Programming Percy

Hey, I'm Percy Bolmér. I'm a software engineer, writer and tech enthusiast. I've been professionally building software for 7+ years, and absoluetly love sharing any ideas I come across.

Email : programmingpercy@gmail.com

Website : https://programmingpercy.tech

About Percy Bolmér

Hey, I'm Percy Bolmér. I'm a software engineer, writer and tech enthusiast. I've been professionally building software for 7+ years, and absoluetly love sharing any ideas I come across.

Useful Links