Mutations

NOTE: See Mutation Classes for an updated mutation API.


Relay uses a strict mutation API for modifying the state of your application. This API makes mutations predictable to the client.

On the client-side, Relay also requires you to specify how it should interpret the response from your GraphQL server, which may require your server-side mutations to return payloads with specific fields.

Mutation root

To add mutations to your GraphQL schema, define a mutation type and pass it to your schema:

# Define the mutation type
class Types::MutationType < GraphQL::Schema::Object
  # ...
end

# and pass it to the schema
class MySchema < GraphQL::Schema
  query QueryType
  mutation MutationType
end

Like QueryType, MutationType is a root of the schema.

Mutation fields

Members of MutationType are mutation fields. For GraphQL in general, mutation fields are identical to query fields except that they have side-effects (which mutate application state, eg, update the database).

For Relay-compliant GraphQL, a mutation field must comply to a strict API. GraphQL::Relay includes a mutation definition helper (see below) to make it simple.

After defining a mutation (see below), add it to your mutation type:

class MutationType < GraphQL::Schema::Object
  # Add the mutation's derived field to the mutation type
  field :add_comment, field: AddCommentMutation.field
  # ...
end

Relay mutations

To define a mutation, use GraphQL::Relay::Mutation.define. Inside the block, you should configure:

Whereas you can have whatever combination and number of input_fields you wish, Relay expects different return fields when using certain mutator configuration you use on the client-side:

For example:

AddCommentMutation = GraphQL::Relay::Mutation.define do
  # Used to name derived types, eg `"AddCommentInput"`:
  name "AddComment"

  # Accessible from `inputs` in the resolve function:
  input_field :postId, !types.ID
  input_field :authorId, !types.ID
  input_field :body, !types.String

  # The result has access to these fields,
  # resolve must return a hash with these keys.
  # On the client-side this would be configured
  # as RANGE_ADD mutation, so our returned fields
  # must conform to that API.
  return_field :post, PostType
  return_field :commentsConnection, CommentType.connection_type
  return_field :newCommentEdge, CommentType.edge_type

  # The resolve proc is where you alter the system state.
  resolve ->(object, inputs, ctx) {
    post = Post.find(inputs[:postId])
    comments = post.comments
    new_comment = comments.build(authorId: inputs[:authorId], body: inputs[:body])
    new_comment.save!

    # Use this helper to create the response that a
    # client-side RANGE_ADD mutation would expect.
    range_add = GraphQL::Relay::RangeAdd.new(
      parent: post,
      collection: comments,
      item: new_comment,
      context: ctx,
    )

    response = {
      post: post,
      commentsConnection: range_add.connection,
      newCommentEdge: range_add.edge,
    }
  }
end

Derived Objects

graphql-ruby uses your mutation to define some members of the schema. Under the hood, GraphQL creates:

Each of these derived objects maintains a reference to the parent Mutation in the mutation attribute. So you can access it from the derived object:

# Define a mutation:
AddCommentMutation = GraphQL::Relay::Mutation.define { ... }
# Get the derived input type:
AddCommentMutationInput = AddCommentMutation.input_type
# Reference the parent mutation:
AddCommentMutationInput.mutation
# => #<GraphQL::Relay::Mutation @name="AddComment">

Mutation Resolution

In the mutation’s resolve function, it can mutate your application state (eg, writing to the database) and return some results.

resolve is called with:

It must return a hash whose keys match your defined return_fields. (Or, if you specified a return_type, you can return an object suitable for that type.)

Specify a Return Type

Instead of specifying return_fields, you can specify a return_type for a mutation. This type will be used to expose the object returned from resolve.

CreateUser = GraphQL::Relay::Mutation.define do
  return_type UserMutationResultType
  # ...
  resolve ->(obj, input, ctx) {
    user = User.create(input)
    # this object will be treated as `UserMutationResultType`
    UserMutationResult.new(user, client_mutation_id: input[:clientMutationId])
  }
end

If you provide your own return type, it’s up to you to support clientMutationId

Specifying a Return Interface

An alternative to defining the whole return type from scratch is to specify return_interfaces. The result of the resolve block will be passed to the field definitions in the interfaces, and both interface-specific and mutation-specific fields will be available to clients.

MutationResult = GraphQL::InterfaceType.define do
  name "MutationResult"
  field :success, !types.Boolean
  field :notice, types.String
  field :errors, types[ValidationError]
end

CreatePost = GraphQL::Relay::Mutation.define do
  # ...
  return_field :slug, types.String
  return_field :url, types.String
  return_interfaces [MutationResult],

  # clientMutationId will also be available automatically
  resolve ->(obj, input, ctx) {
    post, notice = Post.create_with_input(...)
    {
      success: post.persisted?
      notice: notice
      url: post.url
      errors: post.errors
    }
  }
end