How to handle GraphQL query batching in Golang

Josephine Suwanto
SafetyCulture Engineering
4 min readJul 10, 2018

--

Gopherize yourself! Ashley McNamara’s gorgeous artwork

As a GraphQL consumer, we want to increase query efficiency of data delivery by decreasing the amount of resources required for each data request. Query batching allows the client to batch multiple queries in a single request and is supported by the Apollo Client.

The client then has the ability to run a few distinct queries within a single round-trip. For example: the client is trying to render a screen with multiple view components, and each component requires an API call to yield the data for the display.

In order to be able to fulfil this particular client need, the server will have to have the ability to understand how to handle these type of request.

The Batch Request

Upon batch queries within one request, the client will be sending an array of objects whereby they can define each query independently. For example, when the client is attempting to get a group’s name and an image, a request may look like:

POST https://yoursite.com/graphql
[
{
"operationName": "getGroupName",
"variables": {
"groupId": "234"
},
"query": "query getGroupName($groupId: ID!) { group(id: $groupId) { name } }"
},
{
"operationName": "getImage",
"variables": {
"imageId": "123"
},
"query": "query getImage($imageId: ID!) { image(id: $imageId) { thumbnailURL } }"
}
]

Execution

Bare in mind that batch request have multiple queries that need to be executed individually.

First of all, you need to parse the request body into an array of pre-defined Golang struct so that we can effectively access these information throughout the code. Assuming that the struct used for the single request is GraphQLRequest , the working parser would look like the following.

/* Read the request JSON */
body, err := ioutil.ReadAll(request.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
/* the array of pre-defined struct */
var requests []*GraphQLRequest
/* Parse JSON body */
err := json.Unmarshal(body, &requests)
if err != nil {
return nil, err
}

Here comes the most exciting part, the query execution. Since there are multiple queries in the request, we now need to loop through the list of queries, and execute them one by one. Make sure to capture the result of each query as a response to the client request.

var results []*graphql.Response
var wg sync.WaitGroup
for reqOrder, r := range requests {
wg.Add(1)
/* the goroutine that contains query execution in it */
go func(order int, req *gqlRequest) {
defer wg.Done()
results[order] = GraphQLSchema.Exec(ctx, req.Query, req.OperationName, req.Variables)
}(reqOrder, r)
}
/* this is going to wait for all query to be executed */
wg.Wait()
All adorable gophers created by Takuya Ueda.

Benefits

Better user experience; at SafetyCulture, we are customer obsessed. We always think about better ways to provide value to our customers. This includes providing seamless experience throughout the application. One way to contribute to this effort is to minimise the amount of time needed to serve the data that the customer asks for.

Query batching is one of the ways to bring quicker time interval for the API calls. The faster the server in serving the data needed by the client, the faster the client to render the data to be displayed to the user. This means less time staring at the loading spinner, waiting for the client to serve the user’s data. Save time, save lives! 💪

More robust server. Adding the implementation to cater for query batching means that the server is now capable of recognising the various type of requests coming in, such as a single query and batch queries request. Furthermore, it also has the ability to process these types of request in different manner. This then gives the client more flexibility to choose the best request option type based off of their situations.

Pitfalls

Here are some common mistakes you need to be aware of when you batch queries.

Not mapping the result to the correct query. Make sure that the result of each query execution is placed in the order of the queries in the request. Often times the client is expecting the result to be ordered in a certain way. Returning a randomised list of results could potentially cause the client to think that the server is returning bad data. In the worst case scenario, this could result in app crashes because the query results are misplaced in the response.

Overloading a request with too many queries. Only batch queries that makes sense to be grouped within the same request. For instance, you don’t want to be making a batch request with a hundred queries just for the sake of increasing bandwidth. Remember, at the end of the day, it is about implementing a maintainable code base for the next engineer to use. If a request with a single query does the job, why batch it? ¯\_(ツ)_/¯

Happy query batching! 💃

If you’re keen to build solutions that can change the world, SafetyCulture is looking for like minded people to join us on our journey.

--

--