Skip to content

Instantly share code, notes, and snippets.

@serras
Created September 30, 2020 19:01
Show Gist options
  • Save serras/2272ed7c8c01e5e52bc28f8f0d95e76d to your computer and use it in GitHub Desktop.
Save serras/2272ed7c8c01e5e52bc28f8f0d95e76d to your computer and use it in GitHub Desktop.
Better mutations for GraphQL

Better mutations for GraphQL

GraphQL is great. The first time I read about it I remember thinking that it had everything: a powerful schema language, a way to describe exactly the data you want, even an appealing syntax. With time, though, the second of those sentenced have been bugging me: it's great to describe data you want, but for modifications, it doesn't feel as well-rounded.

Whereas GraphQL gives you a nice language for queries, for mutations it's customary to have just a top-level method where the input object carries all the information. Take the following example, obtained from the GraphQL.js tutorial:

type Message {
  id: ID!
  content: String
  author: String
}

type Mutation {
  createMessage(input: MessageInput): Message
  updateMessage(id: ID!, input: MessageInput): Message
}

Why is the id a parameter to the mutation? Wouldn't it be nicer to be able to use the same query language to figure out which is the message to update, and then use inner calls for the required changes?

mutation {
  message(id: 3) {
    setAuthor("Alejandro")
  }
}

The spec defines the structure of top-level query, mutation, and subscription as types. That means that, in theory, you could expose such an interface. Still, most developers and even most guides, simply assume that all your mutations are going to be top-level methods in the mutation type.

The problem, in my opinion, is the enforced separation of query and mutation schemas. Instead, I would love to see mutations being part of each type:

type Message {
  id: ID!
  content: String
  mutation setContent(s: String)
  author: String
  mutation setAuthor(s: Author)
}

type Root {
  message(id: ID): Message!
}

Requests would still not be allowed to mix queries and mutations, but that can be easily enforced by ensuring that the leaves of the request are all mutations. That way we can use the mutation request exemplified above.

Since I'm simply dreaming, we could even go a step further and have special sugar for a pair of field and its corresponding mutation, similar to a property in many languages. This would be the schema definition:

type Message {
  id: ID!
  mutable content: String
  mutable author: String
}

Accordingly, this would be an example of mutation:

mutation {
  message(id: 3) {
    author := "Alejandro"
  }
}

And then, GraphQL would really be perfect for me.

Appendix: where does this come from

I really like to spend time thinking about how we access and manipulate data. The optics framework is for me the best answer at a conceptual level, and I've been busy taking part of a JS library and spreading the word. In the rest of the post I'll assume you know what I'm talking about, otherwise you can look at any of the previous links.

GraphQL gives us getters (when we have a mandatory field), partial getters / affine traversals (when we have an optional one), and folds (when we have a field with an array type). But optics give you also the other side of the coin: setters, and that's what's missing for me. By thinking on the optics level, we have a framework to explain how focusing on part of your data interacts with the operations that set it. To be precise, I'm not asking for introducing the full power of setters, since they allow for arbitrary transformation. Instead, each type would provide a restricted amount of setters, or mutations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment