GraphQL mutations: Partial updates implementation
How to manage partial changes on existing resources with GraphQL mutations
In REST, we can use PUT or PATCH HTTP verbs to manage server-side changes, with PUT to update an existing resource and PATCH to apply a set of changes. In GraphQL, you have to create mutations to modify server-side data, and different implementations are possible to support partial updates.
This article will focus on the implementation that seems the most “natural” to us thanks to some features introduced in GraphQL-js v0.8.0 with its support for null values as field arguments.
I’ll use CodeSandbox.io for my live examples, so you can just play the queries or change the GraphQL server code directly to do your own experiments.
Important: I recommend either duplicating the code (via the Fork menu) for live testing, or downloading the sample.
PUT equivalent
In this new sandbox we’ve created a new mutation called updateAuthor to update an author:
type Author {
id: Int!
firstName: String
lastName: String
}type Mutation {
updateAuthor(authorId: Int!, firstName: String, LastName: String): Author
}
The updateAuthor resolver:
updateAuthor: (_, { authorId, firstName, lastName }) => {
const author = find(authors, { id: authorId });
if (!author) {
throw new Error(`Couldn’t find author with id ${authorId}`);
}
author.firstName = firstName;
author.lastName = lastName;
return author;
}
In this version of the updateAuthor resolver, the mutation input field values are used to update an existing author. Because the firstName and lastName input fields are defined as non-required in the schema, you can set their values to null if needed.
If one input field is omitted in the mutation the corresponding value is set to null, which means that this solution is not usable for partial updates.
This mutation updates firstName and lastName with the provided input field values:
mutation {
updateAuthor ( authorId:1, firstName: "Aldous", lastName: "Huxley")
{
id
firstName
lastName
}
}=>
{
"data": {
{
"id": 1,
"firstName": "Aldous",
"lastName": "Huxley"
}
}
This mutation implicitly sets lastName to null:
mutation {
updateAuthor ( authorId:1, firstName: "Aldous")
{
id
firstName
lastName
}
}=>
{
"data": {
{
"id": 1,
"firstName": "Aldous",
"lastName": null
}
}
This mutation explicitly sets lastName to null:
mutation {
updateAuthor ( authorId:1, firstName: "Aldous", lastName: null)
{
id
firstName
lastName
}
}=>
{
"data": {
{
"id": 1,
"firstName": "Aldous",
"lastName": null
}
}
Partial update management
This other sandbox includes a new version of the updateAuthor resolver:
updateAuthor: (_, {authorId, firstName, lastName}) => {
const author = find(authors, { id: authorId });
if (!author) {
throw new Error(`Couldn't find author with id ${authorId}`);
}
if (firstName !== undefined) {
author.firstName = firstName;
}
if (lastName !== undefined) {
author.lastName = lastName;
}
return author;
}
In the resolver function, before updating the author fields, some tests check if the corresponding property is defined in the mutation arguments.
if (firstName !== undefined) {
author.firstName = firstName;
}
Destructuring syntax is used in this resolver to unpack values from the arguments.
But if you use this syntax for your resolver (without destructuring the arguments):
updateAuthor: (root, args, context)
you can check if the property is defined in args like this:
if (args.firstName !== undefined) {
author.firstName = args.firstName;
}
This mutation works as expected: lastName is not altered (no implicit null).
mutation {
updateAuthor ( authorId:1, firstName: "Aldous")
{
id
firstName
lastName
}
}=> {
"data": {
{
"id": 1,
"firstName": "Aldous",
"lastName": "Davis"
}
}
With this pattern, a non-required input field can:
- have a value,
- have a value set to null, or
- not exist (no changes to do on the existing resource)
You can add some logic in the resolver to reject null values for some non-required fields if needed according to your business rules.
Partial updates using query variables
We use query variables in the mutation like this:
mutation ($authorId: Int, $firstName: String, $lastName: String) {
updateAuthor($authorId, $firstName, $lastName)
{
id
firstName
lastName
}
}
Partial updates can be managed by defining the variables for the fields we want to update and omitting the ones we don’t want to update. For example, to update firstName only, the query variables will be:
{"authorId": 1, "firstName": "Aldous"}
Conclusion
This implementation of partial updates in GraphQL is straightforward for API users since they only have to use a single mutation per resource (e.g. updateUser) to manage PUT and PATCH operations by including the input fields corresponding to the values to update on the server-side.
In our front-ends using Apollo Client (React-Native and React), we only have to define the query variables to update when calling the mutations; there are no new GraphQL query templates to create.
Other implementations and patterns are possible thanks to GraphQL’s flexibility and openness, and you can choose the one that best fits your requirements and constraints.