An introduction to GraphQL — Part 1 — How did we get here?
You may have heard the term GraphQL doing the rounds over the last couple of years. You may have even heard it’s technology developed by Facebook, but like all new-ish tech, there’s still a lot of mystery surrounding it. So what is it exactly?
Essentially GraphQL is a specification, not an actual implementation. However, at the end of the day, with the amount of libraries implementing it, it may as well be a set of implementations.
So back in 1999, a new technology was emerging as a communications protocol. This was known as SOAP(Simple Object Access Protocol). This was based on XML which given the verbose, ASCII nature of it, was quite heavyweight.
One thing SOAP had going for it and it’s verbose nature was its type safety, code generation and “human readable” text.
Given how difficult SOAP ended up being to work with, it gave way to REST. Now technically REST isn’t an actual standard, but makes use of a set of standards. And even so, many “RESTful” implementations didn’t end up following all of these standards.
One of the great things about REST is that it’s based on JSON which makes the responses much lighter weight than their SOAP cousins. The downside of this is that we have given up a lot of type safety and implied documentation which goes along with that.
To get around this limitation, the industry turned to Swagger (which is now known as OpenAPI). Typically defined in yaml, it was the API documentation to your REST data. The downside to this, like a lot of documentation, it can get out of sync with the actual implementation.
So as SOAP was replaced with REST, it seems like the industry is replacing REST with GraphQL.
What does GraphQL give us? For starters, we have a clearly defined schema which is only transmitted when you require it (for example during code generation). This brings back the type safety we lost from the transition of SOAP to REST. However we’ve kept the lighter style of JSON (well, JSON-like) requests and responses.
The next big thing is that it allows the client to only request the data it actually needs. This can be a HUGE saving. For example, Netflix did a write-up of their transition from REST to GraphQL, and they saw pages go from fetching 10Mb of data to 200Kb.
In this transition of moving the data field requests to the client side, the server now handles all the endpoints to fetch multiple pieces of data. So rather than the client having multiple locations to fetch from, it talks to a single endpoint then the server fetches on behalf of the client much like an API gateway or a “Backend for frontend” style server.
One of my favourite features is that documentation is defined in code. Every field has to be defined to a type (which can be a structure containing even more types) plus the actual documentation is defined in the code, reducing any chance of mismatch between a Swagger definition and the actual API. Much like code comments however, this does require developer discipline to update, but when it’s right in your face, it’s less likely to be forgotten.
Unlike the pattern that has emerged from REST with endpoints being versioned (which really isn’t a great pattern anyway), GraphQL standard has a single endpoint. Fields within the schema can be deprecated or added to without breaking your client that may be using an older schema. There are a few design decisions that make future extensibility easier, but it seems to me that it’s a cleaner solution. However, this could be considered a downside to GraphQL as well. Also, there are a number of tools that can be run in your CI/CD pipeline to compare schema versions and inform you if you’re making a breaking change.
The libraries that make this possible on the server side simplify a lot of the work behind this. Each part of your schema can have a resolver defined (the whole schema must be covered, but this can be done at various depths within the schema), with the library handling asynchronous calls from those and synchronisation of responses before forwarding to the front end. This brings along with it a mental model shift of moving from thinking of endpoints on the client to more thinking about the data that you require which at the end of the day is what is important to our apps.
Most of the server side libraries implement either GraphiQL or Playground which allow you to play around with requests without having to put it in code, compile and run. If your API is only for private consumption (eg. internal app), this can be turned off for production releases.
So this all sounds fantastic right? There has to be something it doesn’t do well? And yes, there is. Like everything with computing, it’s all about trade-offs. So what are some of the traps in GraphQL?
The biggest one I see (which I’ll gladly be proven wrong on this if someone considers one bigger) is that because the client only has a single connection to the server, and doesn’t know about the endpoints, requests can get bogged down. For example, you request data that on the server will have to go out to two different endpoints. One of these may resolve within 10 milliseconds, the other one 20 seconds. The client won’t get any data back for 20 seconds. Of course documentation and education helps with this, but it can still be a trap.
Beyond that, multiple resolvers may have overlapping requirements on endpoints, potentially having them make the same request as another resolver, duplicating load. This comes about because every resolver is written to be standalone and not require data from other parts of the request. There is a workaround for this however, and a couple of libraries to help with this. You can add a layer behind your GraphQL resolvers but in-front of any endpoint that will cache data for that request.
The GraphQL spec only defines the data protocol, nothing beyond that, so the transport layer you use isn’t defined. However, more often than not, this tends to be http/https and POST requests. Given that the spec is fairly light, there is no authentication or authorisation defined. Because resolvers are self contained, it limits the ability for example to send login data AND request data at the same time. This is also a limitation of REST. So the typical flow is that you’ll need to log in, get some sort of token for that (JWT for example), and send it in the header of your requests making it all a two step process.
One part of GraphQL I don’t consider a plus or a minus over REST is mutating data. When designing your schema, in my experience so far, it seems as if you just convert your REST POST/PUT/DELETE into parts of the mutation schema, thus no real benefit beyond being able to do multiple at once. However the requesting of data FAR outweighs this.
In the next part of this series, we’ll build a very simple server.