Living APIs, and the Case for GraphQL

It’s hard to read exactly where GraphQL stands in the API world right now. Available publicly since 2015, trends in APIs aren’t obviously moving in its favor, but not obviously moving against it either. Interest from the developer community has been steady throughout even if the technology isn’t spreading like wildfire.

Its biggest third party proponent is GitHub, who released the fourth version of their API as GraphQL in 2016 with an engineering post speaking about it very favorably. It also has a other vocal users in the form of Shopify and Yelp, both of whom offer public GraphQL APIs. But beyond those big three, other big providers are somewhat harder to find. This repository keeps a list of publicly available GraphQL APIs, and most well-known API providers are notably absent, including Facebook themselves 1.

Most publicly proffered APIs are still “REST-ish” – with resources and actions offered over HTTP – including those from almost every name you’d recognize in the space: Amazon, Dropbox, Google, Microsoft, Stripe, and Twilio. Momentum plays a huge part in that the pattern is widespread and developers are used to it both on the parts of integrators using APIs, and those who are building them. Some arguments are still made that strict adherence to REST and hypermedia will open a wide world of automatic discoverability and adaptation, but lack of real world precedent despite years of opportunity seems to be a strong empirical suggestion that this vision is a will-o’-the-wisp.

GraphQL’s biggest problem may be that although it’s better, it’s not “better enough”. The bar set by REST is low, but it’s high enough to work, and is adequate for most purposes.

I’ve been doing a lot of thinking about what a new generation of web APIs would look like (or if there will be one at all), and I for one, would like to see more GraphQL. I’ll try to articulate a few arguments for why it’s a good idea that go beyond the common surface-level selling points.

The surface

I’ll defer to the official introduction as a good resource to get familiar with GraphQL’s basics, but it has a few important core ideas that are worth touching upon.

With GraphQL, fields and relationships must be requested explicitly. Here we ask for a user object including the currency, email, and subscriptions fields:

getUser(id: "user_123") {
  currency,
  email,
  subscriptions
}

There’s no wildcard operator like a SELECT * from SQL. Compared to REST, this has an advantage of reducing payload size (especially helpful for mobile), but more importantly, it establishes an explicit contract between the client and server which allow APIs to be evolved more gracefully. We’ll talk about this more below.

GraphQL is automatically introspectable online. By using the special __type operator, any client can get a detailed understanding of a type and all its fields and documentation:

{
  __type(name: "User") {
    name
    fields {
      name
      type {
        name
      }
    }
  }
}

Every common implementation supports introspection (it’s required in the GraphQL spec) and tooling can be built to rely on it being available. Unlike REST, there’s no need to retrofit an unstandardized description language like OpenAPI (or its myriad of competitors). Even today, these are usually not available, and often not completely accurate because the description isn’t tied directly to the implementation.

Finally, GraphQL is typed. Types often come in the form of complex objects (e.g., User) or JSON scalars (e.g., int, string), but the type system also supports more sophisticated features like enumerations, interfaces, and union types. Nullability is baked in, which happens to work out incredibly well when building APIs in languages that don’t allow null (like Rust) because every field comes out as non-nullable by default. This additional constraint makes handling API responses more deterministic and less prone to error.

The relationships between people in a town are a graph. This is a stretch (but I like this photo).

The graph

As its name would suggest, GraphQL models objects as a graph. Technically, the graph starts with a root node that branches into query and mutation nodes, which then descend into API-specific resources.

GraphQL takes existing API paradigms to a logical conclusion. Almost every REST API that exists today is already a graph, but one that’s more difficult to traverse. Resources reference other resources by IDs (or links in APIs which most strongly adhere to the principles of REST), and relations are fetched with new HTTP requests. Making relationships explicit is conceptually sound, and lets consumers get work done with fewer API calls.

Stripe’s API has a concept called object expansion that lets a user tell the server that it would like an ID (e.g., cus_123) expanded into its full object representation by passing an expand[]=... parameter in with the request. Expansions are chainable, so I can ask for charge.customer on a dispute to reveal the dispute’s associated charge, and that charge’s customer. The feature’s most common effect is saving API calls – instead of having to request two objects separately, just one request can be made for the first object with the second embedded. Users make extensive use of this feature – we constrain expansions to three levels deep, but get regular requests to allow up to four levels.

Discovery and exploration

A core challenge of every API is making it approachable to new users, and providing interactive way to explore them and make ad-hoc requests is a great way to address that. GraphQL provides an answer to this in the form of GraphiQL, an in-browser tool that lets users read documentation and build queries.

I’d highly recommend taking a look at Shopify’s public installation and trying some for yourself. Remember to use the “Docs” link in the upper right to pop open and explore the documentation. You should find yourself being able to build a query that delves 4+ relations deep without much trouble.

Using GraphiQL to explore an API and graph.

A vanilla installation of GraphiQL is a more powerful integration tool for users than what 99% of REST providers have, and it’s available automatically (modulo a little configuration for authentication, CORS, etc.), and for free.

It’s also worth remembering that GraphiQL’s features are built right onto the standard GraphQL introspection primitives – it’s just an HTML and JavaScript file that can be hosted statically. For a big provider, building a custom version of it that’s tailored to the features and layout of a specific API is well within reason.

Batch operations

Every sufficiently long-lived web API that responds to user feedback will eventually evolve a batch API.

In REST APIs, that involves building a custom batch specification because there’s nothing even close to wide standardization for such a thing. Users adapt to each exotic implementation by reading a lot of documentation. In GraphQL, batch queries are built right in. Here’s a document containing multiple operations on the same query and which uses aliases (userA, userB) so that the results are disambiguated in the response:

userA: getUser(id: "user_123") {
  email
}

userB: getUser(id: "user_456") {
  email
}

Batch mutations are also allowed.

The availability of this feature doesn’t necessarily give users free reign the ability to run costly batch requests. Remember that as an API provider, you can still put restrictions on this within reason. For example, by allowing only five operations per request (if that’s the right fit for you), or even just one.

Explicitness and graceful enhancement

I mentioned above how fields in GraphQL must be requested explicitly and that there’s no SQL-like glob operator (SELECT *) to get everything. This might be GraphQL’s most interesting feature because it lends itself so well to API versioning and enhancement.

In a REST API, an API provider must assume that for any given API resource, every field is in use by every user because they have no insight at all into which ones they’re actually using. Removing any field must be considered a breaking change and an appropriate versioning system will need to be installed to manage those changes.

In GraphQL, every contract is explicit and observable. Providers can use something like a canonical log line to get perfect insight into the fields that are in use for every request, and use that information to make decisions around product development, API changes, and retirement. For example, when introducing a new field, we can explicitly measure its use over time to see how successful it is. Alternatively, if we notice that a field is only in use by a tiny fraction of users and it fits poorly into the API’s design or is expensive to maintain, it’s a good candidate for deprecation and eventual removal.

Living APIs

The REST model of little insight tends to produce APIs with a strong tendency to ossify, with broad and abrupt changes made intermittently with new versions. GraphQL produces an environment that evolves much more gradually.

Fields that need to be phased out can be initially hidden from documentation by marking them with GraphQL’s built-in deprecated annotation. From there, providers may choose to even further restrict their use by gating in users who were already consuming them, and disallowing everyone else, possibly with an automatic process to remove those gated exceptions as users upgrade organically over time and move away from those deprecated fields. After a long grace period, their use can be analyzed, and product teams can start an active outreach campaign for total retirement before removing them entirely.

Similarly, new fields are introduced one at a time and their adoption can be observed immediately. Like a living thing, the API changes little by little. New features are added and old mistakes are fixed. It trends towards maturity incrementally in a distant perfect form.

In the ideal case, we produce APIs that grow and improve like living things. My hands were really cold when I shot this.

Shared convention and leverage

GraphQL introduces many powerful ideas, and because it was written in response to extensive real-world experience, it addresses API scaling problems that most would-be API designers wouldn’t think about until it was too late.

It comes with a comprehensive spec to help avoid ambiguities. The result is that most GraphQL APIs look very similar and features are widespread throughout all common implementations. I’d personally like to see its designers take an even more opinionated stance on conventions like naming, mutation granularity, and pagination, but even without, it’s still a far more sophisticated set of constraints than what we have with REST. This forced consistency leads to leverage in the form of tools like GraphiQL (and many more to come) that can be shared amongst any of its implementations.

REST’s momentum may appear unstoppable, but underdesign and loose conventions leave a lot to be desired. We’d be doing ourselves a favor by keeping our gaze on the horizon.

1 Common wisdom is that GraphQL at Facebook is sequestered to internal APIs only, although the public API they do offer is graph-like and could fairly be called proto-GraphQL.

Living APIs, and the Case for GraphQL was published on June 8, 2018 from San Francisco.

Find me on Twitter at @brandur.

Please post comments and discussion to Hacker News.

Did I make a mistake? Please consider sending a pull request.