Mutations are GraphQL operations that modify data on the server and return the updated results.
Mutation Structure
A mutation consists of five elements:
- type -
mutation (required)
- name - Optional mutation name
- variables - Optional variable definitions
- directives - Optional directives
- selection - One or more fields from schema mutation fields
mutation CreateUser($name: String!, $email: String!) {
createUser(name: $name, email: $email) {
id
name
}
}
Three Ways to Define Mutations
1. Inline Schema Definition
Define mutation fields directly in your schema:
app/graphql/app_schema.rb
module GraphQL
class AppSchema < GraphQL::Schema
mutation_fields do
field(:create_user, 'User', null: false) do
argument :name, :string, null: false
argument :email, :string, null: false
perform do |name:, email:|
User.create!(name: name, email: email)
end
end
field(:update_user, 'User', null: false) do
argument :id, :id, null: false
argument :name, :string
perform do |id:, name: nil|
user = User.find(id)
user.update!(name: name) if name
user
end
end
end
end
end
Alternatively, use the call option:
app/graphql/app_schema.rb
module GraphQL
class AppSchema < GraphQL::Schema
mutation_fields do
field(:create_user, 'User', call: :perform_create) do
argument :name, :string, null: false
end
end
# By default, uses the bang version: create_user!
def self.create_user!(name:)
User.create!(name: name)
end
# Or specify custom method with call:
def self.perform_create(name:)
User.create!(name: name)
end
end
end
2. Standalone Mutation Classes
Define mutations as separate classes:
app/graphql/mutations/create_user_mutation.rb
module GraphQL
module Mutations
class CreateUserMutation < GraphQL::Mutation
desc 'Create a new user'
argument :name, :string, null: false
argument :email, :string, null: false
argument :role, 'Role', default: 'USER'
def resolve(name:, email:, role:)
User.create!(
name: name,
email: email,
role: role
)
end
end
end
end
Import into your schema:
app/graphql/app_schema.rb
module GraphQL
class AppSchema < GraphQL::Schema
import_into :mutation, Mutations::CreateUserMutation
# Or import all mutations
import_all Mutations, recursive: true
end
end
Standalone mutations use the class name as the field name. CreateUserMutation becomes createUser.
3. Source-Based Mutations
Sources can automatically generate mutations:
app/graphql/sources/user_source.rb
module GraphQL
class UserSource < GraphQL::Source::ActiveRecordSource
self.assigned_to = 'User'
# Automatically provides:
# - createUser(input: UserInput!): User!
# - updateUser(id: ID!, input: UserInput!): User!
# - deleteUser(id: ID!): Boolean!
end
end
Mutation Arguments
Mutations typically use Input types for complex data:
Mutations have a special perform step that executes before resolution:
app/graphql/mutations/create_post_mutation.rb
module GraphQL
module Mutations
class CreatePostMutation < GraphQL::Mutation
argument :title, :string, null: false
argument :body, :string, null: false
# The resolve method acts as the perform step
def resolve(title:, body:)
Post.create!(
title: title,
body: body,
author: context.current_user
)
end
end
end
end
Inline mutations use the perform block:
mutation_fields do
field(:create_post, 'Post', null: false) do
argument :title, :string, null: false
perform do |title:|
Post.create!(title: title, author: context.current_user)
end
end
end
Perform Context
The perform step has access to:
def resolve(title:, body:)
object # The source object (usually the schema)
request # The current GraphQL request
context # Request context (current_user, etc.)
# Example: Check authorization
raise GraphQL::UnauthorizedError unless context.current_user
Post.create!(title: title, body: body, author: context.current_user)
end
Mutation Examples
Create Operation
mutation {
createUser(name: "John Doe", email: "[email protected]") {
id
name
email
createdAt
}
}
Update Operation
mutation {
updateUser(id: 1, input: { name: "Jane Doe" }) {
id
name
updatedAt
}
}
Delete Operation
mutation {
deleteUser(id: 1)
}
With Variables
mutation CreatePost($title: String!, $body: String!) {
createPost(input: { title: $title, body: $body }) {
id
title
body
author {
id
name
}
}
}
Variables:
{
"title": "My First Post",
"body": "This is the content..."
}
Multiple Mutations
mutation {
createPost(input: { title: "Post 1" }) {
id
}
createComment(input: { postId: 1, body: "Great!" }) {
id
}
}
Unlike queries, mutations in a single operation execute sequentially in the order they appear.
Return Types
Mutations can return various types:
app/graphql/app_schema.rb
module GraphQL
class AppSchema < GraphQL::Schema
mutation_fields do
# Return the created object
field(:create_user, 'User', null: false)
# Return a boolean
field(:delete_user, :bool, null: false) do
argument :id, :id, null: false
perform { |id:| User.find(id).destroy! }
end
# Return a custom payload type
field(:update_user, 'UserPayload', null: false)
end
end
end
Payload Types
Create payload types for complex responses:
app/graphql/objects/user_payload.rb
module GraphQL
class UserPayload < GraphQL::Object
field :user, 'User', null: false
field :errors, :string, array: true
field :success, :bool, null: false
end
end
app/graphql/mutations/create_user_mutation.rb
def resolve(name:, email:)
user = User.new(name: name, email: email)
if user.save
{ user: user, errors: [], success: true }
else
{ user: user, errors: user.errors.full_messages, success: false }
end
end
Validate mutation inputs:
app/graphql/mutations/create_user_mutation.rb
module GraphQL
module Mutations
class CreateUserMutation < GraphQL::Mutation
argument :input, 'UserInput', null: false
def resolve(input:)
# Access input fields
input.args.name # => "John"
input.params # => { name: "John", email: "..." }
# Validate
raise GraphQL::ValidationError, 'Name too short' if input.args.name.length < 3
# Use input resource (if assigned)
user = input.resource
user.save!
user
end
end
end
end
Error Handling
Handle mutation errors gracefully:
app/graphql/mutations/create_user_mutation.rb
def resolve(name:, email:)
user = User.create!(name: name, email: email)
rescue ActiveRecord::RecordInvalid => e
# Errors will be included in the response
raise GraphQL::ExecutionError.new(
e.message,
extensions: { code: 'VALIDATION_ERROR', field: 'user' }
)
end
Or use rescue_from in the schema:
app/graphql/app_schema.rb
module GraphQL
class AppSchema < GraphQL::Schema
rescue_from ActiveRecord::RecordInvalid do |exception|
{ errors: [{ message: exception.message }] }
end
end
end
Mutation Options
Call Method
Specify which method to call:
mutation_fields do
# Calls create_user! by default
field(:create_user, 'User')
# Calls custom_create method
field(:create_user, 'User', call: :custom_create)
end
Use inline blocks for simple mutations:
field(:delete_user, :bool) do
argument :id, :id, null: false
perform do |id:|
User.find(id).destroy!
true
end
end
Best Practices
- Use Input types - Organize complex arguments in Input types
- Return the modified object - Let clients request the fields they need
- Validate inputs - Check data before mutations
- Handle errors gracefully - Use rescue_from or try/catch
- Use payload types - Return structured responses with errors
- Check authorization - Verify permissions in perform step
- Keep mutations focused - One mutation should do one thing
- Name clearly - Use verb + noun pattern (createUser, updatePost)