This tweet by Phil Sturgeon a week or so ago stirred the pot of a lot of GraphQL enthusiasts.
Around the same time, this project called Vulcain was announced. (Which by the way looks great!) Part of the announcement included *“TL/DR: you don’t need #GraphQL anymore!”. This week, Mark Nottingham posted an amazing article on the power of HTTP/2 and what it means for API design.. And finally, here's another in same vein: https://twitter.com/darrel_miller/status/1183425699677376515
All of these did make me think about this subject some more. When everything in this world starts running HTTP/2 (and HTTP/3), do we all have no more reasons to use GraphQL? Let’s dive into it a little bit.
OK, so let’s start by understanding what HTTP/2 brings to the table that would change the value of GraphQL. There are a ton of new things in HTTP/2 like a new binary format and better header compression, but the thing that makes the most sense for us to talk about in the context of GraphQL is the way HTTP/2 handles delivery of requests/responses.
Opening TCP connections is a costly operation that a lot of HTTP/1 clients want to avoid. For this reason, developers have often tried to limit the amount of requests because of their large overhead, with things like batching, query languages, inlining css/js, sprites, etc. HTTP/1.1 attempted to solve some of these issues with persistent connections and pipelining. These two things allowed browsers to send multiple requests and responses over the same connection. The problem is this was quite vulnerable to head-of-line blocking, meaning one slow request could slow down every other behind it. The smart people behind HTTP/2 solved this issue in a different way: Along with this new binary protocol comes a new delivery strategy. HTTP/2 opens a single connection, but multiplexes requests and responses using a new binary framing layer, where each frame is part of a stream. Clients and servers are then able to reconstruct requests and responses stream numbers present on each frame. This allows HTTP/2 to very efficiently handle a lot of requests over a single connection.
Not only that, but HTTP/2 has a new concept called Server Push. Without going into too much detail, server push allows servers to preemptively send responses before the client requests them. The best example for this are stylesheets and javascript assets. While responding to an HTTP request, a server could detect an HTML page it is rendering contains a stylesheet and anticipate the following request for the stylesheet, already sending the response before the client asks for it. This is what Vulcain, the project we talked about at the beginning of the article uses to efficiently fetch linked resources.
OK, cool, but what does this even have to do with GraphQL? Let’s dive into it.
GraphQL: A Single Request to Rule Them All
Part of the appeal for GraphQL is that it helps us deal with those expensive HTTP/1 connections better. That’s because GraphQL lets client select every single possibility exposed by the server within a single round trip. Compare this with a hypermedia focused API, which usually requires a lot of network requests (Something caching can help with).
https://graphql.org/ uses this as a “selling” point.
Most people who are declaring GraphQL useless with HTTP/2 are specifically talking about this point. Batch APIs, query languages like GraphQL, embedded relations, and even bigger custom endpoints become way less appealing in that sense if the cost of making requests become small, and that’s totally true! But is that the only reason we use GraphQL? I don’t think so.
Gotcha: It’s still early days for both HTTP/2 clients and certain application servers
This one is not a good excuse but it is worth mentioning. Using HTTP/2 at the application layer is far from a solved problem in some ecosystems. I’ll let you google for Rack/Rails over HTTP/2, it’s quite something. This is because a lot of application servers have been built with the request/response pattern, and starting to think in terms of those HTTP/2 streams is not an easy task, especially for certain frameworks. But, it’s not a good excuse, a lot of ecosystems do support it well, and in theory, we should still aim to make this better! (Most proxy servers do support it though, but it’s hard to do things like server pushes if the app server is stuck in that req/response pattern.)
GraphQL is not only about reducing round trips or about over/under-fetching
While we see these two things being advertised a ton, GraphQL gives us a lot more than the ability to reduce round trips, or to reduce the number of bytes transmitted over the network.
GraphQL’s power, and where it’s making the most tradeoffs is how client-centric it is. Over the past few years this has been a concern in the minds of a lot of people. Daniel Jacobson wrote a bunch of amazing articles 5–7 years ago about some of these issues. Here’s another one.
Our REST API, while very capable of handling the requests from our devices in a generic way, is optimized for none of them.
Notice that this isn’t necessarily about REST needing us to make more requests, or transmitting unnecessary data. It is more about designing an API that enables support for multiple and different client use cases. A common way to handle this problem is to let client logic be ran closer to the server, like Netflix’s client adapters mentionned in that 2012 post. Since then, some teams there have even been using GraphQL. The BFF pattern is another abstraction that aims to solve similar issues.
GraphQL redefines that client server boundary by helping us build a server engine capable of compiling client use cases into server resources. Persisted queries make this even easier to see, being essentially client generated server resources.
GraphQL as a server side abstraction is something to keep in mind when discussing whether it’s still relevant in an HTTP/2 world. While maintaining a variety of use cases can cause issues in typical endpoint based APIs, GraphQL enables API providers to focus on exposing many possibilities without needing to think about the cost on existing clients and without the added complexity of maintaining tons of different resources. (It does come with its costs: hard to optimize performance, not always very cacheable. The same tradeoffs highly customizable APIs often come with)
The Client Developer Experience
I tend to focus a lot on server side stuff in these posts, but it’s important to remind ourselves that GraphQL is especially loved at the client level! GraphQL fragments are an absolutely awesome abstraction when paired with the component pattern we see in modern frontend frameworks. Again, paired with persisted queries, the GraphQL client developer experience can be quite incredible.
It’s the Whole Package that Makes it Great
GraphQL isn’t inherently special, and yes alternatives will exist! Typed schema? Just use OpenAPI! Server-side abstraction to handle multiple client use cases? There are many ways to do that. Introspection? Hypermedia can allow clients to discover actions and can start from the root too. The amazing GraphiQL? I’m sure there’s something for OpenAPI. It’s always possible to recreate parts of what GraphQL has. However, it’s everything together, found under one specification that made GraphQL appealing and great to so many of us. I suspect that’s also what made it grow so fast also. Because there’s so much guidance into building this API style, language specific libraries for GraphQL tend to be high quality and well adopted.
The Network is Still (will always be?) A Constraint
Here’s a last thought. No matter how fast network requests will be, it seems like they will always remain some sort of constraint. This is why we don’t design web APIs like typical programming language objects for example:
Snippet from Patterns of Enterprise Application Architecture: https://martinfowler.com/books/eaa.html
While HTTP/2 definitely favours finer grained requests, I think the tradeoff still exists.
Can HTTP/2 help GraphQL?
OK so GraphQL brings us tons of other important things, but HTTP/2 is still really awesome. Something to possibly look at in the future is if we can bring some of this power to GraphQL too. Something like this:
query {
viewer {
name
posts(first: 100) @stream {
title
}
}
}
Where we can still use GraphQL’s server side abstraction, it’s declarative query language, while using HTTP/2 streaming. This is something that’s been explored at Facebook, using web sockets I believe. I need to look into this more, but I know people are already exploring directives such as @defer, @stream and @live.
Final Words
HTTP/2 is awesome, and the example in this article in particular is quite mind blowing! And if GraphQL is simply a way to reduce the number of round trips you’re making, or about saving bytes, chances are you’ll be happy with an HTTP/2 powered endpoint-based API! However If you’ve also felt the other powers we’ve discussed in this article, you probably also realize that GraphQL brings us much more than this.
Thanks for reading 💜
— Marc
I Just released a book on building GraphQL APIs. If you liked this article, you might love this book. Check it out if you’re interested: https://book.productionreadygraphql.com