David Chanin I'm a full-stack developer working at EF Hello in London. I’m the maintainer of Hanzi Writer, a JavaScript library for Chinese character stroke animations and quizzes, and I built Wordsheet.io.

Robust GraphQL mutations the Relay way

3 min read 1098

GraphQL

When you are first learning about GraphQL, there’s a few basic traps you may fall into related to API design for mutations that can come back to bite you later on.

Fortunately, if you follow a few simple principles when designing your GraphQL mutations, you’ll be in great shape.

In this article, we’ll look through the Relay API spec for mutations by Facebook and walk through best practices to follow for your mutations (whether or not you use Relay on the frontend).

Use a unique output type for each mutation

At first, it seems intuitive to have mutations on a resource just directly return that resource in GraphQL.

However, this leads to brittle mutations that cannot expand to add metadata to responses or other resources as your needs change.

Instead, Relay recommends that every mutation have its own unique output type with the suffix Payload.

For example, if you have a mutation called CreateUser, its output type would be called CreateUserPayload.

Let’s make this clearer by looking at an example.

Let’s imagine we’re building a simple blog API.

At first, you may be tempted to make a CreatePost mutation that looks like this:

mutation CreatePost(title: String!, body: String!): Post

This looks simple enough: our mutation just takes a title and body and directly returns a Post object.

The problem comes later when we realize we would also like to add the current viewer object to refresh the list of posts on the viewer’s home screen as well.

Currently, there’s no way to add more fields to this output, because the output type is Post, which is likely used in lots of other places.

By not using a unique output type for this mutation, the output of the mutation is locked to whatever the fields in Post are.

Let’s look at this same mutation using a Relay-style output type.

Now, our mutation looks like this:

mutation CreatePost(title: String!, body: String!): CreatePostPayload!

type CreatePostPayload {
  post: Post
}

Using a Relay-style output type makes it easy to add more fields to the output.

If we want to add the current viewer, we could add it like so:

mutation CreatePost(title: String!, body: String!): CreatePostPayload!

type CreatePostPayload {
  post: Post
  viewer: Viewer!
}

This would allow us to send a mutation like this:

mutation {
  createPost(title: "My New Post", body: "New Post Body") {
    post {
      id
      name
      body
    }
    viewer {
       dashboardPosts {
         id
         name
       }
    }
  }
}

Likewise, if we want to add more fields to the mutation response, there’s no problem – this mutation is future-proof!

Use a single, unique input-type for each mutation

While it’s not as essential as having a single, unique output-type for each mutation, it’s also a best practice to have a single input parameter called input for each mutation.

For Relay, this parameter is indicated with the name of the mutation and the suffix Input.

As your mutations get larger and larger and accept more and more parameters, it can quickly become difficult to keep client GraphQL in sync during development.

Moving a large parameter list into an input object like this is known as the Parameter Object Pattern, and can help simplify your code.

For example, for our CreatePost mutation above, we can rewrite the input using a CreatePostInput type like this:

mutation CreatePost(input: CreatePostInput!): CreatePostPayload!

type CreatePostInput {
  title: String!
  body: String!
}

This lets us use our mutation like this:

mutation createPost($input: CreatePostInput!) {
  createPost(input: $input) {
    post { ... }
  }
}

Now, no matter how many more fields we add to the createPost mutation, the above GraphQL is still correct.

Idempotent mutations with clientMutationId

One of the stranger parts of the Relay mutation spec is the parameter clientMutationId, which shows up in both input and output types for mutations.

This parameter is very powerful though, since it allows your mutations to be made idempotent.

For example, imagine in our above example that a client sends a createPost request, but they lose Internet for a moment during the request. How can the client tell if the request went through?

Should it retry again when it gets network access again? If we send another request, but the server did receive the previous request, we’ll end up with a duplicate post.

Fortunately, we can solve this with the clientMutationId parameter.

This requires both the client and the server to be aware of this parameter. It allows our server to, for instance, cache the response for each client based on the clientMutationId they sent.

So, if the server sees two requests with the same clientMutationId, it will realize that the second request is a duplicate and return the cached response again without reapplying the content of the mutation.

This allows the client to get the response content it needs, without accidentally causing the mutation to run multiple times on the server.

By using this parameter, we’ve essentially made all of our mutations idempotent.

The Relay spec also requires sending the same clientMutationId back to the client in the output of the mutation.

This is to help clients ensure the response they’re getting is for the mutation they requested in cases where there may be multiple similar mutations sent in quick succession.

Use a verb for mutation names

While not strictly enforced by Relay, the spec recommends naming your mutations using a verb.

Mutations take an action in your API, so it makes sense to use an active word to indicate that some change will occur by calling the mutation.

For example, you should name a mutation with a verb like createPost rather than with a noun like postCreation.

Standardized implementation

An additional benefit of sticking with the Relay spec for mutations is that, as a common spec for GraphQL, it’s easy to implement.

Most common GraphQL libraries have support for Relay, like Graphene for Python, or graphql-js for Node/JavaScript.

These libraries make implementing the patterns described above easy, so you don’t need to worry about remembering to create separate named input and output types for each mutation or adding an optional clientMutationId param.

It’s still up to you to make your code aware of clientMutationId if you’d like to use it, though.

Conclusion

Relay is a good spec to follow for designing your GraphQL API even if you use Apollo or another GraphQL client on the frontend.

It’s officially supported by Facebook, the creator of both GraphQL and Relay.

They’re industry standards, and the design opinions Relay imposes on mutations will help to ensure your mutations are robust and future-proof as your API grows.

Happy mutating!

200’s only : Monitor failed and slow GraphQL requests in production

While GraphQL has some features for debugging requests and responses, making sure GraphQL reliably serves resources to your production app is where things get tougher. If you’re interested in ensuring network requests to the backend or third party services are successful, try LogRocket.https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
David Chanin I'm a full-stack developer working at EF Hello in London. I’m the maintainer of Hanzi Writer, a JavaScript library for Chinese character stroke animations and quizzes, and I built Wordsheet.io.

Leave a Reply