Living APIs, and the Case for GraphQL — Brandur Leach
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 .
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 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.
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
either 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.
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.
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.
Did I make a mistake? Please consider sending a pull request.