This is a new post in my GraphQL Mutation Design series. Last week, we talked about what I called Anemic Mutations. This week, we’re touching a new subject: How to design mutations so that clients can use the power of static queries.
We often talk about how powerful GraphQL Clients are, but sometimes we forget that depending how the GraphQL Schema is designed, thing may be much more awkward on the client side.
Static queries already have a good rep in the GraphQL ecosystem. It’s hard to argue against them since they bring so many advantages to building great clients and apps.
When I say about static queries, I’m talking about GraphQL queries that will always have the same query string no matter what the variables are at runtime. This is done by using GraphQL variables and directives to alter runtime behavior.
There are several advantages to this. Amongst others, this leads to easier code generation to handle these queries, persisted queries, better tooling, and better predictability. I find that Sashko Stubailo did a great job explaining why they’re great a while ago in his post: https://dev-blog.apollodata.com/5-benefits-of-static-graphql-queries-b7fa90b0b69a.
When building a GraphQL schema, there’s an important thing that I think we should keep in mind: A client should never have to dynamically build query strings or use a query builder.
Optimizing Mutations for Static Usage
Now that we know how useful building static queries is, we need to make sure we design our schema in a way that clients are not forced to dynamically build their queries.
Let’s take the e-commerce example from the last post:
We decided not to use a fat data-centric mutation and use a finer-grained mutation to add an item to our checkout. From a server side perspective, this is pretty great to maintain. However, if we start thinking about clients, especially when it comes to keeping queries static, we quickly see that we might hit problems.
Imagine this scenario: we’re building a shopping cart UI that let’s users build up a cart of items, and when they are ready they may press the checkout button to start the payment process. Easy! Let’s say we don’t care so much about losing the carts so we’ll have our cart in local storage, and then when the user is truly ready, we’ll use
addItemToCheckout to commit the items to a checkout.
You might be seeing the problem already. We truly have no way of doing this using a static GraphQL query given this schema design. In fact, if we don’t want to make
n network calls, our client might end up having to do something like this:
Since the field
addItemToCheckout only lets us add a single item to the checkout with a set of variables, we need to use the field
n times for
n items. And because of that… our client has to build up a dynamic query string to achieve that, a big no when it comes to static queries!
Plural arguments, not plural fields
There’s a pretty simple way to enable this use case while keeping our mutation static. As we said earlier, variables are one way to modify our queries at runtime without changing the query string. What if instead of having to use our field
n times, we simply passed
n variables instead?
Now that our input can accept any number of items through
items, we can build up a query string that will never change in function of the number of items. Only the variables we send will change:
As you can see, even though GraphQL gives power to clients, theres a way to give them an easier time by designing our GraphQL schema the right way.
Now, that doesn’t mean you can never expose a “singular” field in your queries or mutations. What it does mean is that maybe you should expose the plural version too if its a valid use case. In fact, a good example of this are the
nodes field in Relay, so this is not only for mutations!
Designing mutations is tricky because we need to know our clients very well to make good decisions. I find that by following these guidelines, it makes things just a bit easier.
Thank for reading ❤️ As always, if you’ve enjoyed this post, you could follow me on twitter! In the next post we’ll explore an issue that arrises when we use multiple mutation fields in a single request. Stay tuned!