Backend technologists would typically say that GraphQL came as “the shiny new kid on the block” after REST APIs. Initially developed by Facebook to be a powerful data-fetching API for internal use, today it powers billions of API calls daily.
The official documentation defines GraphQL as a query language for APIs that gives a 360 degree view of the data in the API. But a definition from Freecode camp says:
“GraphQL is a syntax that describes how to ask for data, and is generally used to load data from a server to a client. It lets the client specify exactly what data it needs in a request, thereby making it easier to select data from multiple sources.”
This tutorial will highlight some major points to note when implementing GraphQL with Golang. To follow along, it is strongly recommended that you have a working knowledge of:
In any scenario where GraphQL is discussed, some keywords will be used more often than others. As such, it is imperative to remind ourselves of some of those words:
GraphQL types: These are the most basic components of a GraphQL schema definition. They are JSON fields representing the kind of objects that can be fetched from the service. Check out this tutorial to understand GraphQL types in detail.
As an example, here is a sample type definition of an author that has three fields: name
, id
and book
:
type author { name: String! //the `!` sign shows it’s required id: ID! book: String! }
GraphQL queries: GraphQL queries are a way of requesting or fetching data from a GraphQL server. How the queries are structured would determine if data would be over-fetched or under-fetched. This tutorial explains queries in detail.
Here is a sample query on the names of authors, following our type definition above:
query { authors { name } }
GraphQL schema: Schemas are like maps. They help us understand the data by describing their relationships within the server. This helps clients know how to structure their requests when trying to reach the data. They are written in SDLs, i.e Schema Definition Language.
Resolvers: Resolvers hunt for the requested data in the server. They are field specific functions. It is the only mechanism GraphQL uses to return data per query request.
GraphQL mutations: Mutations are used to create and modify objects in GraphQL, similar to PUT and POST in REST APIs. They follow similar syntax as queries, with the exception that they need to start with the keyword mutation
.
GraphQL subscriptions: This is a feature that allows the server to send data to its clients for every specific event it was configured for. To do this, the server maintains a steady connection with the client subscribed to it
The Go programming language, otherwise known as Golang, is a general purpose programming language initially designed by Google. Golang tops the ranks for its simplicity, concurrency, and fast performance, among many other beautiful features. As such, it remains one of the top options when implementing GraphQL.
As many people came to realize the potential of this duo (GraphQL and Golang), software engineers over the years began to build libraries to integrate GraphQL seamlessly with Golang.
Here are some libraries that make the implementation smooth:
We won’t focus on the implementation of any of these libraries in this tutorial. However, each of these libraries has an examples
folder you can navigate to in order to see firsthand how these libraries are implemented.
Let’s explore some scenarios we can experience when integrating GraphQL with Golang and what the solutions are.
The graphql-go/graphql
is a fully-featured implementation of GraphQL in Golang that supports queries, mutations, and subscriptions, among many other features.
Though this library takes advantage of other libraries to help you build production-ready GraphQL servers, it is an official reference implementation of GraphQL.js. In other words, it is the Golang version of the GraphQL.js library.
The benefit of its near-identical API to GraphQL.js makes it so easy to read another GraphQL schema written for Node.js/JavaScript, and you’ll still be able to rewrite it for Golang. Let’s take a look:
GraphQL schema definition for Golang with graphql-go
We import the graphql-go
package and define the GraphQL object type named BaseQuery
with a hello
field to create the schema.
package main import ( "log" "github.com/graphql-go/graphql" ) var queryType = graphql.NewObject(graphql.ObjectConfig{ Name: "BaseQuery", Fields: graphql.Fields{ "hello": &graphql.Field{ Type: graphql.String, Resolve: func(p types.ResolveParams) interface{} { return "World!" }, }, }, }) var Schema, err = graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, }) if err != nil { log.Fatalf("failed to create new schema, error: %v", err) }
Creating the GraphQL server after the schema creation follows the normal steps as highlighted in the official graphql-go
documentation here.
Graphql schema definition for JavaScript with GraphQL.js
From the code snippet below, we require the GraphQL package and define the GraphQL object type named BaseQuery
with a hello
field. This follows a similar process as highlighted in the schema above.
var graphql = require('graphql'); var queryType = new graphql.GraphQLObjectType({ name: 'BaseQuery', fields: { hello: { type: graphql.GraphQLString, resolve: function () { return 'World!'; } } } }); var schema = new graphql.GraphQLSchema({ query: queryType });
Taking a close look at the two representations above, we can conclude that implementing GraphQL in Golang using the graphql-go
library is a walk in the park irrespective of what programming language you’re familiar with.
This is because the graphql-go
library closely mirrors the official reference implementation of GraphQL.js, which is the defacto library of choice. As a result, it provides near-identical APIs to the GraphQL.js library.
Unlike REST APIs that use one resolver(see definition above) per API endpoint, GraphQL executes one resolver per field. This implies that there might be too many additional resolvers per request, especially for nested data. This is the n+1 problem.
Here is an example query to further buttress this:
query { authors { # fetches authors - query 1 name book { # fetches books for each author (N queries for N authors) title } } } # N+1 round trips
Here is the response from the query:
{ "writers": [{ "name": "Tom Wolfe", "book": { "title": "The Bonfire of the Vanities" } }, { "name": "ALice Walker", "book": { "title": "The Color Purple" } }] }
The example above might not show how quickly this can escalate to become a problem. But assuming we need to fetch data for 100 authors, the resolver goes on 100+1 trips to fetch a response. Meanwhile, this could’ve been done in just two trips; i.e, the request and the response.
The fix to this problem is batching.
Batching is the primary feature of a Dataloader. The Dataloader was originally developed by Facebook in 2010 for GraphQL.js to be used as part of an application’s data fetching layer. It provides a simplified and consistent API over remote data sources via batching and caching. However, several other dataloader libraries have been developed for graphql-go
, such as:
gglgen
library for prioritizing type safety. It is also inspired by the official dataloader libraryAnd many others.
Dataloaders (irrespective of the library) solve the n+1 problem by allowing the resolver to request data and return a promise for the data, rather than immediately returning the exact field requested. This then batches all responses from the request and responds at once, hence two trips — request and response.
However, let’s look at the graphql-gophers/dataloader
library in more detail.
Here’s a sample implementation of how the library could be used without cache. For more examples, follow the examples directory of the library here.
package no_cache_test import ( "context" "fmt" dataloader "github.com/graph-gophers/dataloader/v6" ) func ExampleNoCache() { // go-cache will automaticlly cleanup expired items on given diration cache := &dataloader.NoCache{} loader := dataloader.NewBatchedLoader(batchFunc, dataloader.WithCache(cache)) result, err := loader.Load(context.TODO(), dataloader.StringKey("some key"))() if err != nil { // handle error } fmt.Printf("identity: %s", result) // Output: identity: some key } func batchFunc(_ context.Context, keys dataloader.Keys) []*dataloader.Result { var results []*dataloader.Result // do some pretend work to resolve keys for _, key := range keys { results = append(results, &dataloader.Result{Data: key.String()}) } return results }
Typically, coding a GraphQL backend from the scratch involves duplicating quite a number of similar code (schema), e.g different but similar schema for the database and the API endpoint.
This is because the different schema has affected the way the document is stored.
Example: A query on an author from the database
would return an authorID
property, but the same query on an author from the server
directly would return author
property. This makes it difficult to share code between client and server to simplify the codebase.
const fetchBookTitleClient = author => { return author.book.title } const fetchBookTitleServer = author => { const authorDeets = Books.findOne(author.bookId) return authorDeets.title }
A fix for this is server-server queries
In retrospect , this is done by adding the GraphQL schema to the GraphQL function like this:
graphql( schema: GraphQLSchema, requestString: string, rootValue?: ?any, contextValue?: ?any, variableValues?: ?{[key: string]: any}, operationName?: ?string ): Promise<GraphQLResult>
But in implementation, within my code I run the server-to-server queries this way:
const result = await graphql(executableSchema, query, {}, context, variables);
This allows drastic simplification of code without trading performance.
GraphQL is more than just an API query language, as we have seen through the course of this tutorial. It has the potential to totally change the game in terms of data and API queries.
With such potentials, the full benefits of GraphQL must be harnessed by a programming language like Golang.
The Go programming language easily tops the ranks because of its concurrency, simplicity, and fast performance.
I hope you enjoyed reading this post about the gotchas when implementing GraphQL in Golang. Feel free to leave any questions or feedback.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.
LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]