Why use GraphQL?

StockX Tech
8 min readFeb 17, 2021

Kyle Schrade,
Engineer at StockX
@NotKyleSchrade

Before there was GraphQL, there was REST.

In recent years, REST has become the dominant API style for building backend web services. With REST, you could signal the type of request we want to make (ex: GET, POST, PUT, or DELETE) and the resource we’d like to fetch or interact with (ex: /api/pets/1) using an HTTP method and a URL.

It’s a great approach — one we initially used at StockX for several years — but REST comes with some downsides:

  • Over-fetching
  • Multiple requests for multiple resources
  • Waterfall network requests on nested data
  • Clients need to know the location of each service

Enter GraphQL, a server-side runtime and query language for your API.

GraphQL lets you ask for what you want in a single query, saving bandwidth and reducing waterfall requests. It also enables clients to craft requests tailored to their use-case. Down with the endpoint-per-screen pattern!

In the summer of 2019, I wanted to make a case to migrate from our existing RESTful API architecture to GraphQL at StockX. We’ve been running GraphQL in production for over a year now and enjoying the benefits of cleaner documentation, better performance, and increased developer productivity.

In this blog post, we’ll touch upon 7 of the most notable benefits of GraphQL, taken from my original proposal.

Strictly-typed interfaces

The principles of RESTful API design revolve around using HTTP features to communicate intent. If you want to retrieve something, you use a GET request; if you want to create something, you use a POST request; if the response is for an error, you use a non-200 status code. REST doesn’t make any specifications on the payload, meaning the contract between the client and server is loose.

Let’s say the base URL for your RESTful API is /api. If we wanted to add posts as a resource, we could represent that with /api/posts. Based on RESTful design guidelines, executing a GET request on this URL would return an array of posts. Following the same guidelines, we would fetch a single post by executing a GET request on /api/posts/:postId. Note, nothing stops the server-side developers from returning something completely different.

Ultimately, this approach works well as long as everyone is happy with the data that comes back from these endpoints. When that fails, we usually need to provide an on-demand mechanism to add additional fields or sort things, generally through query parameters.

For example, if we wanted to include authors, we could adjust the route to accept another parameter to signal an additional resource type, like /api/posts?include=author. What if we need to sort posts? The URL then becomes /api/posts?include=author&sort=ASC.

Hopefully, you can see that the more we continue to add query params to improve the functionality of the endpoint, the more unmanageable it becomes. This leads to questions like “what’s the correct path to use?” and “what are the valid query parameters I can send, and how do I send them?“. While we typically remember to document the response body, query parameters can stay undocumented for a long time.

GraphQL solves this with a single endpoint and an Interface Definition Language for creating strictly-typed APIs. The GraphQL specification defines how a client interacts with the server, and the schema defines the resources available for retrieval and the accepted parameters when fetching that data.

Smaller payloads

In RESTful API, there are no partial payloads — you get everything in every request, every time. If you don’t need everything, you still eat that bandwidth cost.

In GraphQL, you specify the exact types and fields that you want to receive. If one use-case requires a bunch of properties and another doesn’t, the client can create two different queries that fit each use-case. For client-side applications that depend on an API, reducing the amount of data transmitted could considerably improve their performance.

Better client performance

We already mentioned the ability to reduce payload size, but GraphQL also introduces the ability to minimize round trips to the server.

In a RESTful world, clients request a resource from the server, get the ID for it, then use that ID to fetch another resource; we refer to this as a waterfall of requests.

GraphQL lets you get all the data you need in a single request; no need for follow-up requests.

Less time spent documenting and navigating APIs

Many tools have tried to solve the pain of reference documentation for RESTful APIs, but they all suffer from the same problem. Swagger is one such tool. Swagger docs are great on projects with a single service, but they become expensive to maintain once you get into the microservice world. With Swagger docs scattered across multiple repositories, it can be challenging to keep things accurate and for new clients to get started.

GraphQL is strictly-typed and declarative, which provides a massive improvement over the loosely-typed interface of REST. GraphQL also supports documentation strings in your schema, which makes the process of evolving your schema and updating your documentation highly cohesive.

The community around GraphQL has created a number of tools to explore your graph:

The self-documenting schema coupled with the single entry point makes onboarding and exploration much more straightforward.

No more versioned APIs

APIs evolve. If we don’t plan for this, we are fooling only ourselves. The RESTful design guidelines don’t describe any strategy for evolving your API, but you will encounter some common patterns.

Imagine the product catalog at StockX. We have a RESTful API that provides data about products on our homepage, including their current trading price. At some point, we realize that using a number to represent the price is causing rounding issues on the front-end, and we have to make a change to fix this. It’s no problem to add a new field to the existing API response; new fields are backward compatible and don’t cause issues for consumers. But adding a new field alongside the old field could confuse our consumers since it’s not apparent from the response which field they should trust.

To avoid this confusion, we could add another version of our API, version 2. This new version would provide access to the same underlying information as version 1, but with the price represented as a string. This pattern is known as format versioning. While it seems straightforward, it can introduce a new challenge: what do you do with version 1? Do you continue to maintain this version, or do you deprecate it and help clients move to the new version?

In GraphQL, there’s one version of the graph. To keep track of how the graph changes and evolves, we register it in a schema registry. The registry behaves similarly to that of a version control system like Git and becomes the central place that keeps track of the graph’s current state.

Legacy app support

Let’s consider the example above and assume we’ve added version 2 of our RESTful API, with the plan to deprecate and remove version 1. How long would we have to maintain multiple API versions to support our web and mobile apps?

The update path is relatively straightforward for the web app; once the code is updated, it just has to be deployed to take effect. If we were only supporting a web app, we could remove version 1 of our API at this point.

For mobile apps, the timeline isn’t quite that simple. Once we release the mobile update to the app store, there is an unknown amount of time before every single user updates to that new version. As long as users are running older versions of the app, we have to continue to support version 1 of our API. This process is arduous and requires us to continue to support this API for an unknown amount of time.

We realize an additional issue when considering that the underlying data behind version 1 and version 2 is the same. We have to consider situations where version 1 of the API doesn’t contain fields that version 2 requires, which means that ‘creates’ or ‘updates’ made through version 1 of the API could produce data that isn’t compatible with version 2.

(maybe?) As mentioned above, when a client is consuming a GraphQL API, they specify the fields they want in their query. We can collect and analyze these queries to determine when we can remove deprecated fields.

GraphQL provides an interface that decouples the consumer from the data source; it makes it easy to swap out the underlying data while having a strong guarantee of backward compatibility. Clients can also alias field names in their queries, which means migrating between two field names can be as simple as updating the query.

Better error handling

An area of complexity in traditional RESTful APIs is error handling. The RESTful API guidelines suggest that errors should be communicated using status codes, but this often leads to situations where the response is ambiguous.

Imagine a situation where a client is trying to request a product that doesn’t exist using GET /api/products/bad_product_id. It could make sense to return 404 (not found) since the requested resource doesn’t exist. What if they were trying to add this non-existent product to their cart using POST /api/cart; would we return a 404? That might indicate that we couldn’t find the cart as opposed to the product.

When considering the client-side, handling failure from multiple requests can lead to code bloat and complex logic. To illustrate the complexity, consider the scenario where you have three subsequent REST calls on a page. In the client-side code, you’d have to write the logic that dictates what happens when:

  • call #1 fails
  • call #1 passes, but call #2 fails
  • calls #1 and #2 pass, but call #3 fails

If we were to use GraphQL, the client would specify everything it needs in a single query. GraphQL communicates failure by providing an array of errors in the response body, along with as much of the requested data as it can resolve. Using the same example, we would just return a ProductNotFound in any situation where we couldn’t find a product. If the errors array is empty, there were no errors! The client’s job is now handling partial data instead of managing a partial request chain.

Conclusion

From performance, payload size, developer time, and built-in documentation, GraphQL is the future of APIs. This post covered seven of the tremendous benefits of GraphQL vs. REST.

If you’re just getting started with GraphQL and Apollo, check out the full-stack tutorial. If you’re interested in learning more about how GraphQL can help you get more done faster in your company, download the free GraphQL at Enterprise Scale ebook by the Apollo team.

If you’re interested in changing the game, please apply to join our team.

--

--