Golang: How To Guide
Build Efficient APIs in Go with GraphQL and gqlgen
A step-by-step guide to building robust, flexible APIs in Go
Introduction: Why gqlgen and GraphQL?
Ahhh REST APIs — good ol’ faithful. Simple, straightforward, and easy to use.
They’ve been the “go-to” option for developers for quite a while. After all, who doesn’t want predictable and consistent APIs?
However, use cases are becoming more complex, more applications are requiring sophisticated data relationships, and efficient data retrieval is becoming paramount.
So some of the major drawbacks of using REST API’s are slowly being exposed (e.g., issues with documentation and over/under-fetching of data).
As a result, GraphQL has emerged as a promising alternative by conveniently addressing these concerns.
In fact, one of the main advantages of GraphQL is that it offers a query language similar to SQL, allowing you to retrieve specific data through a single query, rather than relying on multiple requests to various endpoints. This improves efficiency and reduces network traffic, among other benefits.
Additionally, GraphQL provides more flexibility for other developers to consume your API, even with deeply nested data.
There are several libraries available for building GraphQL APIs in Go, each with its own unique set of strengths and weaknesses. But in this article, we’ll be focusing on one of my favorites — gqlgen.
Gqlgen is a schema-first GraphQL library that allows you to quickly build and deploy GraphQL APIs in Go, all while maintaining reliability and consistency.
Overall, gqlgen is an excellent tool to leverage when building GraphQL APIs in Go. My goal here is to show you how you can use it to your advantage.
Ready? Let’s dive in.
❗️TL;DR
While REST APIs have been a popular choice for developers due to their simplicity and predictability, they also have their limitations.
With the need for more complex use cases and efficient data retrieval, GraphQL has emerged as a promising alternative, offering a more flexible and efficient way to consume APIs.
Gqlgen, a schema-first GraphQL library, can help you quickly build and deploy high-quality GraphQL APIs in Go, all while ensuring reliability and consistency.
This is a tutorial on how to use gqlgen to develop GraphQL APIs.
Gqlgen: Configuration & Setup
How gqlgen works
To build our GraphQL API using gqlgen, we’ll start by creating a GraphQL schema. From this, gqlgen will automatically generate all the required Go code for resolvers (i.e., type and fields resolvers) and data structures.
👉 Note: After you make changes to the schema, you’ll need to regenerate the code to ensure our resolvers and models remain in sync with it
Getting started with gqlgen
Assuming you already have an initialized Go project, let’s create a tools.go
file with the following content:
//go:build tools
// +build tools
package tools
import (
_ "github.com/99designs/gqlgen"
)
Next, we’ll want to install our dependencies within our source code by running the go mod tidy
command:
go mod tidy
…and scaffold all the necessary configs needed for our gqlgen workspace:
go run github.com/99designs/gqlgen init
The end result should look like this:
Next, let’s modify our resolver.go
file and add the comment below after the Go imports, which tells the go generate
command how to generate our schema when we run the go generate ./…
command:
//go:generate go run github.com/99designs/gqlgen generate
Now, our resolver.go
should look something like this:
package graph
// This file will not be regenerated automatically.
//
// It serves as dependency injection for your app, add any dependencies you
// require here.
//go:generate go run github.com/99designs/gqlgen generate
type Resolver struct{}
🎯 Pro Tip: Our Resolver struct can be used to inject dependencies such as DB Clients, and we can access them inside our resolvers. For instance, let’s say we build a To-Do application; we then can store our list of tasks in a variable and pass it into the struct:
type Resolver struct {
TodoList []*model.Todo
}
Then, inside one of our resolvers, we can access our list of tasks:
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
return r.TodoList, nil
}
Configuring Gqlgen for flexibility
The flexibility of gqlgen is virtually limitless, and it allows you to configure nearly everything from file directories for generated files to more complex tasks, such as linking GraphQL schemas types to existing Go structures.
This can help you bypass struct generation and link to pre-existing ones, which can provide greater versatility in the development process. You can also configure other items as well, such as:
- Turning on gqlgen tags —i.e., instead of using JSON tags in Go structs, you can use the
gqlgen:fieldName
tag - Disabling and enabling slice pointers
- Automatically binding existing Go structures within a package to corresponding GraphQL types
[This means that if a struct within a “models” directory shares the same name as a GraphQL type, gqlgen will prioritize the existing struct rather than create a new one] - And so much more (see here for more information)
🎯 Pro Tip: You can break up your schemas into multiple files with gqlgen to manage your resolvers easier. So, instead of generating a single Go file with all resolvers, let’s say, the resolver code can be spread across multiple files to match the schema layout structure.
Note: you can always modify this for your specific needs.
GraphQL Schema
Let’s build a simple To-Do application to demo GraphQL. Since gqlgen is a schema-first tool, we’ll start by building the graphql schema.
First, let’s define the type. And to keep it simple, we’ll narrow the fields to just id
, text
and done
.
type Todo {
id: ID!
text: String!
done: Boolean!
}
Next, we need a GraphQL query to fetch our To-Do tasks:
type Query {
todos: [Todo!]!
}
And then, we need GraphQL mutations to create, mark as done, and remove our To-Do tasks:
type Mutation {
createTodo(input: NewTodo!): Todo!
toggleTodoDone(id: ID!): Todo!
removeTodo(id: ID!): Todo!
}
One last thing — even though we’re done with our schema, we’ll want to run code generation before we can proceed and implement our To-Do application:
go generate ./...
👉 Note: This will help generate the To-Do struct and resolvers needed for our queries and mutations.
Resolvers
If you’re new to resolvers, they’re functions that help populate data for our schema from databases, APIs, etc., then return it to the caller.
In our case, we’ll need four unique resolvers:
todos
— a resolver that will fetch all our To-Do taskscreateTodo
— a mutation that will add new To-Do taskstoggleTodoDone
— a mutation that will mark To-Do tasks as complete (or not)removeTodo
— a mutation that will remove To-Do tasks
Let’s go ahead and implement them.
Todos Resolver — Fetching our To-Do tasks
To keep things simple, let’s store our “To-Do” in an array that will be passed to our resolver struct:
type Resolver struct {
TodoList []*model.Todo
}
We’ll also want to create a To-Do variable and pass it to our struct when we initialize our graphql server inside our server.go
(where our main function is currently located).
func main() {
// ... other code generated by our todo
// ... our fake DB
todos := []*model.Todo{}
srv := handler.NewDefaultServer(graph.NewExecutableSchema(
graph.Config{
Resolvers: &graph.Resolver{
TodoList: todos,
},
},
))
// ... move code for starting http server
}
👉 Note: Feel free to replace the basic implementation of the DB here with your DB Client, as well as any other dependencies that your resolvers may require.
Now that that's out of the way, let’s fetch our To-Do list by returning to our application:
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
return r.TodoList, nil
}
If we were using a database, this is where our business logic would go to fetch and do any necessary data transformation ☝️
Ok, let’s move on to mutations.
CreateTodo Resolver — Adding new tasks
Let’s create a new To-Do struct. We need to generate an id
:
id := uuid.New().String()
todo := &model.Todo{
ID: id,
Text: input.Text,
Done: false,
}
…and push the To-Do item to our list:
r.TodoList = append(r.TodoList, todo)
Finally, let’s return our To-Do item:
return todo, nil
Our mutation resolver code should now look like this:
func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
id := uuid.New().String()
todo := &model.Todo{
ID: id,
Text: input.Text,
Done: false,
}
r.TodoList = append(r.TodoList, todo)
return todo, nil
}
ToggleTodoDone Resolver— Mark tasks as complete
For our toggleTodoDone
mutation, we need to find the task matching the provided id
, then toggle and return the Done
field:
func (r *mutationResolver) ToggleTodoDone(ctx context.Context, id string) (*model.Todo, error) {
for _, todo := range r.TodoList {
if todo.ID == id {
todo.Done = !todo.Done
return todo, nil
}
}
return nil, nil
}
RemoveTodo Resolver — Remove tasks
Similarly, for our removeTodo
mutation, we need to find the task matching the provided id
, remove it from the list, and return the removed task.
func (r *mutationResolver) RemoveTodo(ctx context.Context, id string) (*model.Todo, error) {
for i, todo := range r.TodoList {
if todo.ID == id {
r.TodoList = append(r.TodoList[:i], r.TodoList[i+1:]...)
return todo, nil
}
}
return nil, nil
}
And that’s it! Now we can test our application by running the Go server.
go run server.go
You can access the GraphiQL playground via the URL http://localhost:8000/ on your local server and the API endpoint via http://localhost:8000/query
Conclusion
In this post, we built a simple To-Do application in GraphQL API using Go and gqlgen, then explored how we can customize different configurations for gqlgen.
In the next few articles, we’ll build more complex applications with field resolvers, data loaders, GraphQL custom scalers, etc., which will help us create production ready GraphQL APIs. Stay tuned
Main Takeaways
- gqlgen is a schema-first tool that automatically generates the required Go code for resolvers and data structures from a GraphQL schema
- gqlgen is highly configurable and allows you to bypass struct generation and link to pre-existing structures, which can provide greater versatility in the development process
- To build a Go application using gqlgen, you need to create a GraphQL schema, generate resolvers using gqlgen, then implement the code for resolvers (i.e., in our case — to fetch, create, update, and remove To-Do tasks) that will run queries CRUD queries against our database.
Other Articles To Consider
Disclosure: In accordance with Medium.com’s rules and guidelines, I publicly acknowledge financial compensation from UniDoc for this article. All thoughts, opinions, code, pictures, writing, etc. are my own.