DEV Community

Oskar Karlsson
Oskar Karlsson

Posted on

How do you handle breaking changes with GraphQL?

I'm looking for some general tips for designing a GraphQL schema. Let's say you have the following schema:

type Query {
  comments(threadId: ID!): [Comment!]!
  # Get the latest comment by the current user
  latestCommentByUser: Comment!
}

type Mutation {
  # Return true if success
  createComment(comment: CommentInput!): Boolean!
}

type Comment {
  id: ID!
  body: String!
  time: DateTime!
}

It looks good and is easy to understand. The client can get the comments by:

query CommentsQuery {
  comments(threadId: 10) {
    id
    body
  }
  latestCommentByUser {
    id
    body
  }
}

Now, let's say the client wants to create a comment:

mutation CreateComment {
  # Will return true if succeeded
  createComment(body: "This is a comment")
}

Nice! but, we probably want to get the new comment so we can get the id and the timestamp.

So, let's update schema:

type Mutation {
  # Return the new comment
  createComment(comment: CommentInput!): Comment!
}

This is nice but we will probably also need to update our local version of latestCommentByUser (sure, this is a silly example but my point is that we need to get more data in the response). We might be able to do something like:

mutation CreateComment {
  # Will return true if succeeded
  createComment(body: "This is a comment") {
    id
    body
  }
}
query CommentsQuery {
  latestCommentByUser {
    id
    body
  }
}

But can we be sure that the mutation always will run before the query? Is it even valid to do both a mutation and a query in the same request?

It can be achieved by doing something like:

type Mutation {
  # Return the new comment
  createComment(comment: CommentInput!): CreateCommentResponse!
}

type CreateCommentResponse {
  newComment: Comment!
  latestCommentByUser: Comment!
}

We can divide this into two questions:

  1. How do you handle breaking changes in GraphQL? New url path? Just adding new values/methods in the schema and deprecate the old ones? Some other way?

  2. How do you add more data to the mutation response? I find myself in situations, from time to time, when I want to add some data to the mutation request, like in the example above. Should we always return a generic object (like CreateCommentResponse) so we can add and remove data as we go or is it a better way?

Top comments (3)

Collapse
 
itsjzt profile image
Saurabh Sharma • Edited

First of all you shouldn't pass data of queries that would be effected by the mutation inside the mutation payload instead you should refetch the query after mutation or update the local cache. Read more here

How do you handle breaking changes in GraphQL? New url path? Just adding new values/methods in the schema and deprecate the old ones? Some other way?

for adding new values just add them

for breaking changes

  • You can version the queries ex: createComment and createCommentV2
  • you can deprecate the fields without making a breaking change and remove the field when every client updates (you need to have analytics to do this)

How do you add more data to the mutation response? I find myself in situations, from time to time, when I want to add some data to the mutation request, like in the example above. Should we always return a generic object (like CreateCommentResponse) so we can add and remove data as we go or is it a better way?

You can always add more data, but for removing you need to either

  • update all the client (if your apps are the client of your apis)
  • you can version queries or deprecate things like mentioned above
Collapse
 
tjoskar profile image
Oskar Karlsson

Thanks for your reply!

To refetch data after a mutation are a valid solution but it would be nice to fetch the data in the same mutation-request instead of making an extra one, especially when it comes to GraphQL which is all about query data.

Having said that; I just read the spec and it states that one can only make either a query or a mutation request, not both at the same time, and having that stated I only see two solutions: 1. making an extra query (as you suggested) or creating a wrapper type (as described above with the CreateCommentResponse-type).

I guess all mutation could always return the root Query-type to make it possible to query any data after the mutation 🤔 But I think I will just return the changed data to keep it simple.

Collapse
 
itsjzt profile image
Saurabh Sharma

You idea is nice, I think it can work.

Other way is to update the local cache accordingly which means you dont have to query the new data from server apollographql.com/docs/react/cachi...