Why GraphQL is Bad for your API...and How to Fix its Problems

Here is a Well Rounded View of the Problems with GraphQL

By now everyone has heard of GraphQL, it seems to have convinced everybody that REST is no longer the de facto standard of building modern efficient APIs.With a rapidly bludgeoning ecosystem, GraphQL is seeing wide unparalleled adoption...

...it has even been seriously considered a replacement to REST but I can't help but wonder, are there any concrete disadvantages to GraphQL or are they all just peeves? So I decided to do a little research of my own to get a more rounded view - here's why GraphQL is bad.

GraphQL is not the best backend for streaming or real-time live updates apps (Falcor has better support out of the box for this). GraphQL is famously bad at caching, mostly because the alternatives (REST and Falcor) make caching at all levels so easy and efficient. GraphQL is bad at sending anything other than text back and forth (such as file uploads) while REST APIs can do literally anything.

GraphQL is a Disadvantage for Streaming Apps

It might come as a suprise that GraphQL, out of the box, should noe be your best choice for building a backend for streaming applications but, it really isn't.

GraphQL throws away two REST API concepts that prove very useful for this exact purpose.

To understand this lets look at a better approach - Falcor.

Falcor is Better than GraphQL for Streaming Apps

Although Falcor throws away the RESTful idea of a single-resource-to-URL mapping and thus the need for multiple HTTP request, it keeps two things that gives it some considerable benefits over GraphQL for streaming.

Paths and references.

Falcor keeps the idea of a path from REST. This is how it traverses one big JSON object - using paths. Falcor is intimately tied with the idea of a path because that's pretty much its only way for location within a JSON objects.

Paths are actually are actually a part of Falcor syntax.

Because data is identified in Falcor with a unique JSON path, it is much easier to merge data streaming in separate chunks. GraphQL has no such feature out of the box.

By way of example, while a Falcor server is returning an Author object, the UI could already have started rendering. The path would then easy merging of with the data that comes from the server. As Falcor proceeds downloading the author's posts from the API, the UI would already be responsive with user interactions.

This is not to say you cannot implement GraphQL with streaming apps. Ofcourse you can, but you get no such support out of the box. The closest you'll get is GraphQL subscriptions with stream delays.

GraphQL is a Disadvantage for Live Updates Apps

Another REST API idea that GraphQL entirely discards object references.

To contrast this, let's look at Falcor again.

Why Falcor is Better than GraphQL for Live Update Apps

When Falcor represent data in one big JSON object, you end up with a tree format. The JSON format is inherently a tree. But falcor considers your data a "graph" not a tree, how?

The JSON Graph uses References

JSON graphs are the convention for modeling graph data as JSON objects. Exactly the way Falcor does it. JSON Graphs are valid JSON.

        
{
    todosById: {
        "44": {
            name: "get milk from corner store",
            done: false,
            prerequisites: [{ $type: "ref", value: ["todosById", 54] }]
        },
        "54": {
            name: "withdraw money from ATM",
            done: false,
            prerequisites: []
        }
    },
    todos: [
        { $type: "ref", value: ["todosById", 44] },
        { $type: "ref", value: ["todosById", 54] }
    ]
}
        
      

Looking at Falcor's example above, the JSON Graph contains references to other locations in the same object i.e. $type.

It is References that allow graphs to be represented as JSON - hence JSON Graphs.

Why JSON Graphs are perfect for Live Update Apps

This distinction between a tree and a graph is crucial. Graphs are what make Falcor fun to use.

Using both paths (for merging updated data into the old data) and references (updating data linked data) Falcor makes live updates easy to implement. No need for hacks to get efficient data fetches. Much more easier than even REST.

Short story here is Falcor gets streaming and live support for free, but GraphQL has problems on an abstration level.

GraphQL loses it's ability to uniquely identify data with paths to its disadvantage over low traction alternatives like Falcor.

Falcor may be the best option for apps where data streaming and / or real time capabilities are a major part of the app.

GraphQL is bad for Caching

This one is easy.

GraphQL is famously bad at caching, especially HTTP caching - for obvious reasons.

Both REST and Falcor are better at Caching than GraphQL

REST makes caching at all levels easy, almost too easy - seeing as cachiing is a broad complex topic.

HTTP caching is used in REST to easily avoid refetching resource are for identifying similar resources. REST uses URLs in caches as globally unique identifiers that a client then leverages to build caches.

You can already see the problem here. That single GraphQL endpoint.

On a broader level, REST over HTTP uses a whole heap of HTTP best practices and conventions that make HTTP caching a cake walk.

For Falcor, the third benefir you get from retaining paths and references is caching. Falcor caches are consistent without needing to define any special Node or id as GraphQL frameworks like Relay and Apollo must.

Instead of an id, Falcor would just use a path. The data then all comes preformatted with references! No extra hacks for efficient data fetching.

The Fix? Application Caching with Redis.

For GraphQL HTTP caching is out of question, you would have to use application caching with Redis. This adds Redis persistent queries to your toolset.

GraphQl will also not make assumptions about the transport layer, so it also be upto the API developer to build a system can be cached accordingly (it's possible to cache each object seaparately0 - which is rather efficient).

GraphQL is bad at File Uploads

Here, the problem is that GraphQL makes changes in data using Mutations.

CRUD is implemented in GraphQL with Mutations. The data passed to mutations have to be simple JSON objects that negates passing complex JSON objects such as files.

The GraphQL spec and mutations don't accept files in arguments. This makes GraphQL impossible to use for file uploads.

REST is Much Better for File Uploads

One fun parts of REST is file uploads. Leveraging HTTP, REST API developers support easily upload images using JSON request on the same endpoint. They can easily offer URL-based uploads to clients.

A REST API can do literally anything, not just sent text back and forth.

Uploading an image in HTTP often looks something like this:

        
POST /upload HTTP/1.1
Host: localhost:8080
Content-Type: image/jpeg
Content-Length: 1269
Origin: http://localhost:8080
... other headers ...

... raw image content ...
        
      

Using the same endpoint to upload an image with JSON we get.

        
POST /upload HTTP/1.1
Host: localhost:8080
Content-Type: applocation/json
... other headers ...

{
  "image_url": "https://localhost:8080/image.png"
}
        
      

This is often used to send photos directly from local files, as above and from web clients such as image cdns.

Although this approach does not scale well, the approach you are forced to take with GraphQL is through mutations. Being forced to create another Mutation for such a simple task is taking it a bit overboard.

The Fix? Using GraphQL with REST

A simple way to solve this would be to use REST instead.

You could create a REST endpoint to upload files and return the URL. You can then use this URL as a mutation parameter as follows.

        
mutation ($url: String!) {
  updateImageFromRESTEndpoint(input: {url: $url}) {
    url
  }
}
        
      

The REST endpoint could also be declared directly in the mutation instead of being passed in as follows:

        
mutation uploadImage {
  updateImageFromRESTEndpoint(url: "https://localhost:8080/pic.png") {
    id,
    url
  }
}
        
      

Another Fix? Multipart Requests

The multipart request workaround involves appending the file to GraphQL mutations and then sending this to the context of the query execution. The file ends up in the resolver function.

The mutation in this case will no require a url parameter, like in the REST approach above.

In this approach, GraphQL API developers often approach image uploads via S3. This forces a multi-part uploads dependency on clients which could then leak tokens. This approach is arguably, extremely hacky. But it works.