Forest header image

Symfony Finland
Random things on PHP, Symfony and web development

Versioning an API in GraphQL vs. REST

GraphQL is a technology which is a modern and uniform alternative to the wealth of different RESTful interfaces. REST was introduced back in 2000 and is only an architectural style, not a specification. In REST any self-respecting developer is used to versioning, but in GraphQL there is no concept of versioning in the same sense.

Versioning in REST is essential as you get more and more consumers to your API, then some of them will rely on a specific version of the API. There are a few common techniques for achieving this. Using the standard URL method, you will simply publish new versions in unique addresses:

https://example.com/api/v1/movie/12312
https://example.com/api/v2/movie/12312

In the above case there are just two addresses, each of which responds in the exact format throughout times. A another way of specifying the REST API version in the request using HTTP Headers:

GET /api/movie/12312 HTTP/1.1
Accept: application/vnd.api.movie+json; version=2.0

The header approach achieves similar results as the URL, but is possibly harder to cache on the HTTP layer. It's also less explicit to developers, so the URL method is often favoured. As for versioning itself, the industry standard Semantic Versioning scheme works great with APIs, too.

For neither of these there is nothing enforcing the versioning as REST itself is not a standard. Instead you'll need to handle this as a part of the development discipline and also in advance make it publicly known that a certain version is being deprecated.

In GraphQL you introduce and deprecate fields

GraphQL is different from REST in quite a few ways. One of the key thing is that instead of the server dictating what a response will look like, the query itself defines the shape of the response. Instead of sending all the data over the wire, the request specifies exactly what is needed and only that data is sent.

So essentially a GraphQL entity will always remain in the same exact address and represent the same version. Existing data attributes should obviously not be changed to maintain backwards compatibility, as the GraphQL standard itself is unable to enforce this. Discipline and communication is still needed to maintain

Take an example where a company search API has the field for a logo in it. Over the years the API needs to update the type to keep up with the latest file formats:

  • Logo in 2015: Bitmap (PNG)
  • Logo in 2017: Vector (SVG)
  • Logo in 2019: Animated (GIF)

With REST to get the specific version of the logo to maintain compatibility with clients you would end up with three URLs in the year 2020:

https://example.com/api/v1/companies
https://example.com/api/v2/companies
https://example.com/api/v3/companies

Alternatively you you could also keep the older versions of fields around in later versions. That would be feasible with the example case, but if your payload is documents or something else then you'll be sending over a lot of redundant data.

With GraphQL you would simply add completely new fields at each time, but the resource URL will remain the same. Clients that don't request a Bitmap/PNG in 2020 will not even know that it's available.

If we were to add new fields to GraphQL for the new formats over time for a total of three fields (logo, logo_vector, logo_animated) by 2020, then a GraphQL client built in 2015 can continue to make queries like this to get the Bitmap/PNG version:

query Query {
  companies {
    id,
    title,
    logo
  }
}

Instead a bleeding edge client built in 2020 could target the same endpoint with this request to get an animated logo:

query Query {
  companies {
    id,
    title,
    logo_animated
  }
}

This would result in a response with only the animated version of the field. In theory a client could request all of the different fields with a query such as:

query Query {
  companies {
    id,
    title,
    logo,
    logo_vector,
    logo_animated
  }
}

The above query would result in a response like this:

{
  "data": {
    "companies": [
      {
        "id": "9601f717-d7e4-4f52-a655-b3f7f292c3d9",
        "title": "Acme",
        "logo": "/acme_logo.png",
        "logo_vector": "/acme_logo.svg",
        "logo_animated": "/acme_logo.gif",
      },
      {
        "id": "67bab2b1-7230-4a3a-b87b-d2a23dd67371",
        "title": "Facebook",
        "logo": "/facebook_logo.png",
        "logo_vector": "/facebook_logo.svg",
        "logo_animated": "/facebook_logo.gif",
      },
      {
        "id": "835cef66-1a74-4370-912a-09a28afd4ff8",
        "title": "Mitsubishi",
        "logo": "/mitsubishi_logo.png",
        "logo_vector": "/mitsubishi_logo.svg",
        "logo_animated": "/mitsubishi_logo.gif",
      }
    ]
  }
}

Since GraphQL is just a communication level protocol, what happens in the background is completely hidden. In the case of the logo generator, your backend might actually only hold a Master GIF version which it will generate PNG and SVG versions upon request in 2020.

Conclusion

With GraphQL being a specification than an architectural style like REST, you can move from thinking of versioning complete API structures to just managing fields. If some of the fields become too laboursome to maintain or are dropped for some other reason, then you will drop it and accept that some older clients will no longer function. GraphQL backends hold no promise of being around forever.

The GraphQL specification was only released to the public in 2015, but it's been in use at Facebook for a number of years. If you dig out that old iPhone 3GS from your top drawer and fire up the Facebook client, it will likely still work. This is because it already used GraphQL and while there have been plenty changes to the API, the client requests have stayed the same.

In the original introductory blog bost Lee Byron from Facebook describes GraphQL being Version Free as one of it's core characteristics:

The shape of the returned data is determined entirely by the client's query, so servers become simpler and easy to generalize. When you're adding new product features, additional fields can be added to the server, leaving existing clients unaffected.
- GraphQL: A data query language

However things are rarely black and white and both strategies can (and are) applied with both formats. Kévin Dunglas, creator of the API Platform, states that adding fields to REST APIs is a common practice and that GraphQL endpoints are subject to URL changes in cases where there are major changes in the underlying data model:

The [API] versioning strategy isn't bound to the the format [REST or GraphQL] but to the nature of the change.
- Kévin Dunglas

GraphQL reduces the redundancy of sending over data the client does not need and natively aggregates granular REST requests to a purpose built ones based on the customer request. In addition ot also takes away a lot of the considerations developers need to spend time on when versioning REST APIs.

If you're new to GraphQL, read more from the links below:

I would like to thank Kévin Dunglas for valuable feedback to the article.


Written by Jani Tarvainen on Friday August 5, 2016
Permalink - Tags: graphql, rest, javascript, php

« Choosing a front end architecture for Symfony framework projects - PHP/Symfony development with Windows Subsystem for Linux (WSL) »