Building Our First GraphQL Server with Go: An Implementation Guide

Building Our First GraphQL Server with Go: An Implementation Guide

trivago provides travelers with an extensive collection of hotels, empowering them to compare prices and uncover the best vacation deals. With so many exceptional options available, we have introduced a new feature called “Favorites” to streamline the navigation process. This feature enables users to effortlessly save their preferred accommodations and access them later, ensuring ease of use. To access this feature, visit https://www.trivago.com/en-US/favorites.

Behind the scenes

We have begun implementing a robust backend solution to support the feature, putting in the necessary effort to ensure its success. Our original plan was to use a GraphQL server (the GraphQL monolith) as an intermediary layer between the API and the incoming request. This approach meant that the request had to go through another service before reaching the API. The image below illustrates our initial idea and how it was intended to function.

Image 1

It is worth noting that our GraphQL setup is federated, meaning that requests are distributed across subgraphs via a GraphQL gateway. Our GraphQL monolith is one such subgraph. However, communicating with the gateway through the monolith would increase latency and dependencies, so we sought to avoid this. As we already have a GraphQL gateway in place to reduce resource consumption and improve response time, we have opted to leverage it by implementing a dedicated GraphQL server behind the gateway, thereby bypassing the monolith. This change offers us the following benefits:

  • The clients always use the schema of the gateway, changing things behind the scenes won’t affect any of the clients.
  • Since we skip a step by bypassing the monolith, clients potentially should receive faster responses directly from the favorite GraphQL server itself.
  • It is easier to maintain and support changes as we only have to change the code and schema on the favorite GraphQL server instead of also having to adapt the monolith.
  • Go supports running a GraphQL server and a REST API in the same server with a different routing, meaning it is always easy to migrate existing REST APIs when needed.
  • It is quite easy to implement a GraphQL server in Go, the only thing needs to be replaced is the interface and the rest of the implementation remains the same, meaning no additional complex implementation.
  • The Favorite GraphQL Server can fully rely on the GraphQL Gateway for all the credential authentication.

Image below shows the final design.

Image 2

Generating the GraphQL Server

It is quite easy to implement GraphQL servers in Go by using a library called gqlgen. It is possible to initiate a GraphQL server by following command:

go run github.com/99designs/gqlgen init

Then the following folder structure will be created as it states in the official documentation:

├── go.mod
├── go.sum
├── gqlgen.yml               - The gqlgen config file, knobs for controlling the generated code.
├── graph
│   ├── generated            - A package that only contains the generated runtime
│   │   └── generated.go
│   ├── model                - A package for all your graph models, generated or otherwise
│   │   └── models_gen.go
│   ├── resolver.go          - The root graph resolver type. This file wont get regenerated
│   ├── schema.graphqls      - Some schema. You can split the schema into as many graphql files as you like
│   └── schema.resolvers.go  - the resolver implementation for schema.graphql
└── server.go                - The entry point to your app. Customize it however you see fit

After generating the files, the schema of the GraphQL server must be defined, which would be used in the super graph of GraphQL gateway. Once the schema is defined, the models and the resolvers can be generated by running the following command:

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

But it is also possible to implement some make commands to generate the required files:

GRAPHQL = bin/gqlgen
$(GRAPHQL): export GOBIN := $(PWD)/bin
$(GRAPHQL): go.mod
    $(GO) install github.com/99designs/gqlgen
    $(GO) mod tidy

$(addprefix generate-graphql-,$(COMMANDS)):
generate-graphql-%: export PATH = $(GOROOT)/bin:$(PWD)/bin
generate-graphql-%: $(GRAPHQL)
    $(GO) generate ./cmd/$*/...

The rest of the code is pretty much like implementing a traditional REST API in Go. The handler for the GraphQL server has to be initiated, and it is possible to inject dependencies into resolver.go:

qlSrv := handler.NewDefaultServer(
        generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{
            Logger:      logger,
            Service:     service
        }}))

After that, the dependencies will be available to be used in the schema.resolvers.go as it embeds resolver.go as property by default.

Once the schema is defined and the server is generated, query and mutation resolvers will take place in schema.resolvers.go. This will be the handlers file of your GraphQL server. The following example shows the schema definition and the query which is being used to fetch the favorites:

type Query {
  """
  Returns user's saved favorite accommodations.
  """
  getMyFavoriteAccommodations: MyFavoriteAccommodationsResponse!
}

Here is how the code will look like:

type queryResolver struct{ *Resolver }

func (r *queryResolver) GetMyFavoriteAccommodations(ctx context.Context) (*model.MyFavoriteAccommodationsResponse, error) {...}

This function is the handler to return the user’s favorites. From this point, it is the same flow as if you are implementing a traditional REST API, you can just simply implement your business flow here.

Conclusion

Transforming an existing API might be somewhat challenging, but since Go server can support both REST APIs and GraphQL, it is quite easy to switch from one to the other. In our case, the clients were still in the implementation phase, hence we didn’t need to support both REST API and GraphQL server, we simply switched to GraphQL server.

Another important point is to write comments to your schema definitions. When the supergraph collects the schema that you build, it will be presented to the clients, and the more comments you write, the easier it will be for the clients to navigate.

gglgen has a great documentation as well. It will cover all your cases when needed, and we highly recommend taking a look at the examples they provide.