Mutations in GraphQL


In my last post we learned how to integrate GraphQL into Rails project and write first queries to retrieve data from backend.

Now let’s see how we can add, update and destroy entries.

We have new task_type.rb file:

#/app/graphql/types/task_type.rb

Types::TaskType = GraphQL::ObjectType.define do
  name 'Task'

  field :id, !types.ID
  field :title, !types.String
  field :description, !types.String
  field :due_date, types.String

  field :user, !types[Types::UserType] do
    resolve -> (obj, args, ctx) {
      obj.user
    }
  end

  field :updated_at do
    type types.Int

    resolve -> (obj, args, ctx) {
      obj.updated_at.to_i
    }
  end

  field :created_at do
    type types.Int

    resolve -> (obj, args, ctx) {
      obj.created_at.to_i
    }
  end
end

Let’s create mutation_type.rb file. And describe our new Mutations.

#/app/graphql/types/mutation_type.rb

Types::MutationType = GraphQL::ObjectType.define do
  name 'Mutation'
  field :addTask, field: Mutations::TaskMutations::Create.field
  field :updateTask, field: Mutations::TaskMutations::Update.field
  field :destroyTask, field: Mutations::TaskMutations::Destroy.field
end

And add new QueryType:

#/app/graphql/types/query_type.rb

Types::QueryType = GraphQL::ObjectType.define do
  name "Query"
  
  # ...

  field :tasks, !types[Types::TaskType] do
    argument :limit, types.Int, default_value: 10, prepare: -> (limit) { [limit, 30].min }
    resolve -> (obj, args, ctx) {
      Task.limit(args[:limit]).order(id: :desc)
    }
  end

  field :task do
    type Types::TaskType
    argument :id, !types.ID
    description "Find a Task by ID"
    resolve ->(obj, args, ctx) { Task.find_by_id(args["id"]) }
  end
end

Also, add Input object - for us, it’s a simple thing, which should describe input params for our queries.

#/app/graphql/input_objects/task_input_object_type.rb

TaskInputObjectType = GraphQL::InputObjectType.define do
  name 'TaskInput'
  input_field :title, !types.String
  input_field :description, !types.String
  input_field :user_id, types.Int
  input_field :due_date, types.String
end

And finally our TaskMutations module, where we describe input fields, queries, input and return parameters.

#/app/graphql/mutations/task_mutations.rb

module Mutations::TaskMutations
  Create = GraphQL::Relay::Mutation.define do
    name 'AddTask'

    input_field :task, !TaskInputObjectType

    return_field :task, Types::TaskType
    return_field :errors, types.String

    resolve lambda { |object, inputs, ctx|
      task = Task.new(inputs[:task].to_h)

      if task.save
        { task: task }
      else
        { errors: task.errors.to_a }
      end
    }
  end

  Update = GraphQL::Relay::Mutation.define do
    name 'UpdateTask'

    input_field :id, !types.ID
    input_field :task, !TaskInputObjectType

    return_field :task, Types::TaskType
    return_field :errors, types.String

    resolve lambda { |_object, inputs, _ctx|
      task = Task.find_by_id(inputs[:id])
      return { errors: 'Task not found' } if task.nil?

      if task.update_attributes(inputs[:task].to_h)
        { task: task }
      else
        { errors: task.errors.to_a }
      end
    }
  end

  Destroy = GraphQL::Relay::Mutation.define do
    name 'DestroyTask'
    description 'Delete a task and return  deleted task ID'

    input_field :id, !types.ID

    return_field :deletedId, !types.ID
    return_field :errors, types.String

    resolve lambda { |_obj, inputs, _ctx|
      task = Task.find_by_id(inputs[:id])
      return { errors: 'Task not found' } if task.nil?

      task.destroy
      { deletedId: inputs[:id] }
    }
  end
end

Let’s play with GraphQL console:

# add task
mutation addTask {
  addTask(input: {
    task: {
      title: "GraphQL mutation", 
      user_id: 1,
      description: "This article description"
    }
  }) {
    task {
      title
      description
    }
  }
}

# update task
mutation updateTask {
  updateTask(input: {
    id: 14,
    task: {
      title: "GraphQL new mutation", 
      user_id: 1,
      description: "This article description"
    }
  }) {
    task {
      title
      description
    }
  }
}

# remove existing task:
mutation destroyTask {
  destroyTask(input: {
    id: 1,
  }) {
    deletedId
  }
}