Skip to content

Best Practices : Versioning GraphQL APIs #884

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: source
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion site/learn/BestPractice-ThinkingInGraphs.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Thinking in Graphs
layout: ../_core/DocsLayout
category: Best Practices
permalink: /learn/thinking-in-graphs/
next: /learn/serving-over-http/
next: /learn/versioning/
---

## It's Graphs All the Way Down [\*](https://en.wikipedia.org/wiki/Turtles_all_the_way_down)
Expand Down
115 changes: 115 additions & 0 deletions site/learn/BestPractice-Versioning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
title: Versioning GraphQL APIs
layout: ../_core/DocsLayout
category: Best Practices
permalink: /learn/versioning/
next: /learn/serving-over-http/
---

## All About Versioning
> Don't let your application break by some breaking changes, Version your APIs

After a certain period of time, applications and softwares get challenged with some changes in users and business needs. Due to which stability is not compatible with the system while changing only some parts. This is where versioning comes into picture. Versioning of API is done to update the existing API and make it compatible with the updated needs and services. Any change in the existing feature can break the application and so the new versions of API should be developed.

### Versioning of REST
Now let’s have a look on versioning of RESTful API. You’ll wonder why we are talking about RESTful APIs. But gradually you will understand how versioning of GraphQL is much simpler and beautiful than REST. There are many best practices and strategies for REST API versioning such as

* URI Versioning : /api/v1
* Query Parameters : /api?version=1
* Custom Headers : Accept-version : v1

In most of the cases, uri versioning is used for versioning REST API. The client uses a specific version of API for implementations.


### Let's have a look at GraphQL
For GraphQL, you introduce or deprecate the fields or schema instead of versioning. While using GraphQL, one key point is that the query itself decides what response client wants. So there will be only one endpoint while using GraphQL API. We can say that instead of versioning, we are just modifying the existing schema.

Let’s understand by an example.
We have a schema named user, which had fields such as id, firstName and lastName.
```graphql
type User {
id: Int!,
firstName : String!,
lastName : String!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for trailing commas, and the whitespace is inconsistent. May I recommend piping this file through prettier --prose-wrap always to automatically format the markdown (including automatically formatting the GraphQL snippets).

Suggested change
id: Int!,
firstName : String!,
lastName : String!
id: Int!
firstName: String!
lastName: String!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll reformat the file through prettier in VS code. I hope that will fix all format issues.

}
```

In the above schema all fields are required. But in the next version we can add a variable and can also change the requirements. Such as :
```graphql
type User {
Id : Int!,
firstName : String!,
middleName : String!
lastName : String,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few formatting issues, id became capitalised, and lastName becoming nullable was a breaking change.

Suggested change
Id : Int!,
firstName : String!,
middleName : String!
lastName : String,
id: Int!
firstName: String!
middleName: String
lastName: String!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. It was just a typo by me. Fixed in next commit.

}
```

Here we have added the middleName parameter which is mandatory and have changed lastName to optional.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can change an optional field to required, but you cannot change a required field to optional (that's a breaking change). The term field is the correct term to use here (rather than parameter).

Suggested change
Here we have added the middleName parameter which is mandatory and have changed lastName to optional.
Here we have added the `middleName` field which is optional.



* The best practice is to use “@deprecated(reason)” defined in the schema. By using this the client can know which filed to use and which one is deprecated. This will not define the version number as the semantic versioning does. The deprecated fields will be shown by the GraphQL Explorer.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wording of this bullet needs revisiting. Separately, I think it's probably better for it to be a section title such as "The 'deprecated' directive" rather than a bullet.



### More info on Versioning
There are few other best practices for versioning GraphQL APIs.
Version constraints can be added to the query which will tell that the field will be resolved by which version. Using version constraints we can access different fields of different versions. For example, the query will look like :
```graphql
query {
account(id : 5){
old : profilePhoto(versionContraint : “^0.1”)
new : profilePhoto(versionContraint : “^0.2”)
}
}
```

We can define the versions in the schema and query can be written as above to access the fields of different versions.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never seen this recommended as a best practice before (and am very dubious); could you share details of where you've seen this recommended as a best practice? Also typographic quotes have slipped in where you meant straight quotes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually tagging+semantic versioning. I do not remember where I read this, but I'll try to find it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benjie Should I keep this point?


### Evolution of Schema
This is definitely different from versioning, but this will be helpful to build schemas which can be evolved with time without making breaking changes. Schema can be evolved by making changes such as adding/removing fields, structures, types, etc. While making these changes, there can be some points which will restrict the schema while evolving and then @deprecated needs to be used as a last resort. For example there is a schema :
```graphql
type User{
id: String!
name: String!
Username : String!
birthDate : DateTime!
lastLoginHistory : [DateTime]!
}

```
In the above schema we have one field named lastLoginHistory which stores the DateTime fields as a list. Now there needs some changes in the schema and some additional data needs to be added in lastLoginHistory. For that we need to add another field to the User schema. These changes will add many fields and this could make the schema lengthier and worse.

But rather than this, we can build a schema for storing lastLoginHistory data and we can use that schema referenced to the User schema.
```graphql
type NewDateTime : {
lastLoginTime : DateTime!
lastLoginPeriodLength : Time!
}
```
``` graphql
type User{
id: String!
name: String!
Username : String!
birthDate : DateTime!
lastLoginHistory : NewDateTime!
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but... this would be a breaking change for clients?

Copy link
Author

@nisarg1499 nisarg1499 Mar 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ekampf How? I think we can evolve schema by referencing schemas to others. Any specifics why this will be a breaking change?

}
```
This way schemas can be evolved and can be maintained in a better way. Non-required fields should be deprecated but also the new fields should be introduced in that period and the old fields should be removed in such a way that no breaking changes occur.


### Example of Shopify
Shopify versioned their GraphQL APIs using the url versioning approach and made their graphql endpoint such as “/api/{ version }/graphql.json”. [Here](https://shopify.dev/concepts/about-apis/versioning#the-api-version-release-schedule) is the document of shopify. They deprecate the fields and instead name the new variable as newVariablev2.

### Some tools for managing schemas
There are some tools which are used for schema validation, tracking schema versions, identifying unused types and many more. Some of the tools are `Apollo Graph Manager` and `GraphQL Inspector`. Schema can be validated using these tools and you can track all the schema changes.

Using [Apollo Graph Manager](https://www.apollographql.com/docs/graph-manager/), you will be able to validate the schema and get the changes done in schema. For example : you will get to know which field is deprecated, for what reason and which fields are added new, etc. You will be able to validate the schema and check if anything is breaking or not. And you will also be able to know some detailed schema changes provided by this tool.
Apollo schema registry can track the changes of the schema and also gives the option to create variants of the schema. Also you can trace your GraphQL server by getting the report of which operations are being executed, which clients are executing which operations, which parts of schema are most used and which of the resolvers are used most.

Now let’s have a look at GraphQL Inspector which also provides some similar functionalities.

Using [GraphQL Inspector](https://graphql-inspector.com/), you can detect the changes which can be breaking changes for the current schema. You are able to know the restriction changes in fields as well as addition/subtraction in fields. A complete difference is provided between old schema and new schema. And by that you can get to know the differences in form of non-breaking change, dangerous change and breaking change.
In addition you can get the idea of coverage of the schema and can track which part of the schema is used, and by how many times. If any of the fields are deprecated, then the usage of those fields as well as checking of errors can be done using GraphQL Inspector.

### Conclusion
By this way we can version the GraphQL APIs and just move to the game of managing fields instead of versioning. It’s as simple as if you don’t want any field, then just drop it and add necessary fields with appropriate roles.