Home Considering GraphQL? Read this first.
Post
Cancel

Considering GraphQL? Read this first.

Don't let a bloated API crush you.
Don't let a bloated API crush you.

0. Introduction

GraphQL promises to solve of the problems encountered when creating and developing against REST APIs. It addresses over-fetching, where clients receive more data than they need, follow-up requests, where clients need to make multiple requests to retrieve all the necessary data, and attempts to forestall the temptation to over-fit the needs of specific clients (e.g. /books/with-author-and-review-data-included). As with any successful bit of tech, the advantages are obvious, but what’s lost is not. So where does GraphQL fail deliver? And what babies are thrown out with the REST of the bathwater?

1. Illusory Advantages

What apparent advantages don’t hold up to further scrutiny?

1.0. Typed Interfaces

Generating response types for the client to use is extremely helpful. Writing deserialization code for each new request is error prone, time consuming, and repetitive. Make the computer do it!

GraphQL strictly types all inputs and responses all the way to the roots of every object. Apollo provides tools for generating those types. That is, assuming the set of queries is known. This seems like a solid, if inconvenient, win for GraphQL over REST.

However, that would ignore Swagger / Open API. Open API is a standard for defining HTTP interfaces, including request types, response types, and error types, everything you need to make a REST API more comprehensible. Given an interface defined with this standard, there are several tools to generate documentation and client types in multiple languages (1, 2, 3).

1.1. Ease of use for the Clients

Initial use of a GraphQL API requires more setup than a REST API. Though it’s requests are usually JSON over HTTP, graphQL is idiosyncratic enough to motivate either 1) finding an existing client library, or 2) writing a wrapper to handle the requests. By contrast, REST APIs are accessed with simple http requests, the meaning of each each component of a request (method, path, query parameters, headers, body) are defined by convention.

Managing the queries themselves can be annoying as well. Consider how a request for book details might look with a REST API.

1
GET https://bookstore.com/book/lord-of-the-rings

And an equivalent request from a GraphQL API.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
query {
  book(slug: "lord-of-the-rings") {
    title
    id
    slug
    author
    description
    publisher
    publicationDate
    isbn
    pageCount
    genre
  }
}

As the data requested becomes more complicated and the queries get larger, the definitions have to be stored as multi-line strings in the source files, or alongside as resources. The former can be a pain to read, and the latter requires changing the build chain. This is just not an issue with REST APIs.

1.2. Front-End Independence

With a mature graph, front end teams should be able to create or adjust features with minimal coordination from the API team. This greater freedom could allows teams to work faster. However, this does not apply when new features are being created.

When new API features are required, the API team must, by definition, complete their work before the client teams can deliver. It is only when there is a broad set of data available in the graph that this independence can be realized. Letting the API team run ahead to create a large graph without existing client needs is a potentially costly endeavor.

2. Concrete Disadvantages

What are some areas where GraphQL is actually worse?

2.0. Query Resolution is not Trivial

With REST APIs, resolving a query is straightforward. Endpoints are mapped to single entities (often single database tables), and the data returned by each is well known ahead of time. With GraphQL, however, what data is required is not known until runtime. The decisions for what data to load, when to load it, and when to stop are made more difficult by the open-ended nature of GraphQL Queries. The API has taken on the complexities of an SQL database!

2.1. So long HTTP codes

APM and basic monitoring services are easily configured to detect failing requests… as long as they respond with HTTP error codes. The standard in GraphQL is to respond with 200 for all requests that are handled by the GraphQL server. The most popular clients expect this. 5xx can result if there is a database outage, null reference, or other server error. But if the client tries to make an unauthorized access, fetch a missing bit of data, or provides invalid inputs, the standard behavior is to respond 200 and include the errors in the body.

Not only does this make handling errors on the client potentially more complex, but some monitoring tools might not have access to the contents of the response. Unauthorized accesses might go unnoticed!

2.2. Customization over Convention

As stated above, the meaning of verbs, paths, and query parameters are defined by convention. The API team does not need to think about how to define a search endpoint, or how to name the parameters. With GraphQL, the API team must think about how to define the queries, and how to name the parameters. This may appear trivial, but maintaining consistency in a large graph can be tricky. Are search queries named search* or find*, if you use both, what is the meaning of each? Do you provide queries that return single entities? Or do you only provide a search query that returns a list?

With REST all of those questions have ready answers.

3. So you’re going with GraphQL Anyway

Suppose you already have multiple backend services that you want to unify with an API gateway, and there are multiple clients that want access to overlapping data, and the man power to deal with it, yeah GraphQL sure, go for it! Here are some things you can do to make your life easier:

  1. Use a “code-first” library. Creating a schema and then binding all of the mutations, queries and data endpoints is tedious, repetitive, error-prone. Let the computer do it! I really like graphql-kotlin. You can always validate schema changes in CI.
  2. Integrate Data-Loader early. As you grow the graph, you will quickly run into situations where object A contains one or more objects B. If you have a query that results in a list of A’s, then you might also need to return a list of B’s. Without data-loaders, it’s very easy to end up with N+1 queries.
  3. Establish error codes and integrate with monitoring from the start.

As with everything in engineering, there are trade-offs. I hope this post helps you weigh your options. Best of luck!

-->