3. GraphQL Clients – GraphQL for Modern Commerce

Chapter 3. GraphQL Clients

A GraphQL client is software that runs in each client (web browser, native application, etc.) that handles the life cycle of connecting to the GraphQL server, executing queries, and receiving responses.

Technically, any code that allows you to make an HTTP request is a client, from cURL on the command line to any of the thousands of JavaScript libraries that are available. Every programming language, framework, operating system, and so on has the means of making HTTP requests because HTTP-based API calls underpin modern IT.

Note

Even though GraphQL is technically agnostic to the underlying transport protocol, HTTP is the only one you’ll see out in the real world.

Formal GraphQL clients offer basic HTTP request handling plus the following:

  • Low-level networking

  • Batching

  • Authentication

  • Caching

  • Language-specific bindings

  • Frontend framework integration

Let’s explore each one of these in greater depth.

Low-Level Networking

Making an HTTP request sounds simple, but things can go wrong in the long, complicated journey from a client to the GraphQL server and back. The GraphQL server might not respond, the response might be slow, there might be errors calling the underlying datastore, the response size could be very large, and so on. The real world is full of problems. The networking stack of your client can greatly help with these issues by allowing you to configure the following:

  • Retry policies (how many times to retry, how long between each retry, etc.)

  • Limits on response sizes

  • Timeout limits

Depending on your client, you can even swap out HTTP for another protocol.

Batching

The entire point of GraphQL is to allow your clients to retrieve everything needed to render a page or other experience with one request. In an ideal world, you’d have one HTTP request per page.

Frontend frameworks like React encourage modularization. Each “component” in React should be more or less self-contained, including fetching data. Here’s an example of a very simple component:

import React from 'react'
import {Query} from 'react-apollo'
import gql from 'graphql-tag'

const Price = () => (
  <Query
    query={gql`
      {
		product(id: $id) {
			price
        }
      }
    `}
  >
    {({ loading, error, data }) => {
      if (loading) {
      	return "Loading..."
      }
      if (error) {
      	return "Error!"
      }

      return "{data.product.price}"
    }}
  </Query>
)

Imagine having a product detail page composed of 10 of these components, each with their own GraphQL query. Even though this makes the frontend code more manageable from a development standpoint, you’re back to the same problem as REST APIs.

Some clients allow you to batch together multiple GraphQL queries so that you can still have the modularity, but multiple queries are sent to the GraphQL server in one batch. The client will gather all of the GraphQL requests over a period of tens of milliseconds and then submit one request to the server.

Authentication

All GraphQL clients require that you authenticate with the GraphQL server before being able to execute queries. Because GraphQL is served almost exclusively over HTTP, the authentication scheme you’ve been using to secure your REST APIs (typically OAuth 2.0) can easily be reused for GraphQL. Chapter 4 examines this in greater depth.

Authentication typically requires inserting an HTTP header, as follows:

const httpLink = new HttpLink({
	uri: "https://api.myserver.com/graphql",
	headers: {
		authorization: `Bearer ${token}`
	}
});

All HTTP clients, whether GraphQL focused or not, allow you to insert custom HTTP headers.

Caching

Most of the requests handled by a GraphQL server are for queries. As discussed in Chapter 1, queries simply retrieve data; they do not change it. Therefore, many of these queries can be cached locally.

One of the advantages of REST is that it uses the HTTP ecosystem around caching. You could make an HTTP GET to /ProductCatalog/Product/12345 with an HTTP request header of “max-age=180” and your client (or an intermediary) could cache the results. Every piece of software touching the HTTP request between the client and server knows how to handle that HTTP request. With GraphQL, all operations are typically submitted over HTTP GET or POST (though, as we’ll discuss later, GraphQL is transport layer agnostic). HTTP is used as a tunneling mechanism and you’re not able to use the native verbs and caching mechanisms. It’s a different paradigm entirely.

Let’s go back to the very first GraphQL response from Chapter 1:

{
	"data" {
		"product": {
			"brandIconURL": "https://www.legocdn.com/images/disney_icon.png",
			"name": "The Disney Castle",
			"description": "Welcome to the magical Disney Castle!....",
			"price": "349.99",
			"ableToSell": true,
			"averageReview": 4.5,
			"numberOfReviews": 208,
			"maxAllowableQty": 5,
			"images": [
				{ "url": "https://www.legocdn.com/images/products/94695736/1.png", "altText": "Fully assembled castle" },
				{ "url": "https://www.legocdn.com/images/products/94695736/2.png", "altText": "Castle in the box" }
			]
		}
	}
}

In this example, the fields brandIconURL, name, description, max​Al⁠lowableQty, and images are unlikely to change frequently and can therefore be cached. However, price, ableToSell, averageReview, and numberOfReviews are likely to change constantly.

Depending on which client you use, you can specify various cache directives for both the type and the field.

Note

Caching is entirely absent from the GraphQL specification.

Here’s how you’d cache an entire image type for a whole day using the Apollo GraphQL implementation:

type image @cacheControl(maxAge: 86400) {
	id: ID!
	url: String!
	altText: String!
}

Here’s how you’d cache individual fields within a type:

type product {
	id: ID!
	brandIconURL: String @cacheControl(maxAge: 300)
	name: String! @cacheControl(maxAge: 300)
	description: String! @cacheControl(maxAge: 300)
	price: Float!
	ableToSell: Boolean!
	averageReview: Float
	numberOfReviews: Int
	maxAllowableQty: Int @cacheControl(maxAge: 300)
	images: [Image] @cacheControl(maxAge: 300)
}

When a client sees a cacheControl directive, it serves up the data from its local client-side cache. If it doesn’t have the data, it then must call the GraphQL server. The mechanics of caching are entirely dependent upon the client and server vendor, and the client/server often need to work together. As mentioned earlier, caching isn’t in any way part of the formal GraphQL specification. The previous examples were all from the Apollo GraphQL implementation, though other implementations are similar.

It’s extremely unlikely that entire GraphQL queries can be cached for commerce. There’s always at least one field or type that can’t be cached. Therefore, you really need to use an actual GraphQL client, rather than a traditional HTTP client.

Language-Specific Bindings

Frontend developers want to be able to call the GraphQL server using the programming language in which they’re writing the clients. JavaScript developers want a GraphQL client written in JavaScript. Swift developers want a GraphQL client written in Swift. And so on. Even within the programming languages, there are “flavors.” In the JavaScript world, there’s Angular, Vue, Meteor, Ember, and others.

A good client offers the same functionality regardless of the programming language the developer chooses.

Frontend Framework Integration

There are entire frontend frameworks built around GraphQL that include GraphQL client functionality. For example, Facebook’s Relay/Relay Modern are built entirely around React and GraphQL.

Although GraphQL is fast, there’s still a few hundred millisecond delay before data in a user interface (UI) is retrieved and rendered. Popular GraphQL clients can display placeholder data while the actual data is being retrieved. Figure 3-1 presents an example from Facebook, which is using Relay Modern.

Figure 3-1. Progressive rendering

After the content is retrieved over GraphQL and rendered, the screen looks as depicted in Figure 3-2:

Figure 3-2. Fully rendered

Rendering the UI and then painting the data on it dramatically improves the perceived performance.

These frameworks can also get creative with how mutations are handled from a user experience (UX) standpoint. Rather than executing the mutation and repainting the entire page, these frameworks make it possible to update the UI first and then asynchronously execute the mutation against the server.

Final Thoughts

In this chapter, we explained what GraphQL clients are, why they’re better than clients that just handle HTTP requests, and what value specifically GraphQL clients offer.

In Chapter 4, we discuss GraphQL servers.