GraphQL mutations: Partial updates implementation

How to manage partial changes on existing resources with GraphQL mutations

Arnaud Bezançon
May 29, 2017 · 4 min read
Image for post
Image for post

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.

WorkflowGen

Business Process Complexity Management

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store