Back to Blog
Lesson 29 of the GraphQL: Your First GraphQL Schema & Server course
API ArchitectureAugust 16, 20263 min read

Implementing a Simple Mutation: Modifying State in GraphQL

Learn how to implement your first mutation resolver. We'll cover modifying local state, returning the updated object, and ensuring your GraphQL API is responsive.

GraphQLMutationResolversAPIBackend
A top view of a pink pencil and pink paper clips arranged neatly on a pastel background.

Previously in this course, we explored Introduction to Mutations to understand the conceptual difference between read-only queries and data-modifying operations. Now, we’ll move from theory to practice by implementing a concrete mutation to modify our server's local state.

The Anatomy of a Mutation Resolver

In GraphQL, a mutation is just a function—a resolver—that performs a side effect. Unlike a query, which is designed to be idempotent (fetching the same data repeatedly should yield the same result), a mutation is expected to change the state of your application.

To implement a mutation, you follow three steps:

  1. Define the Mutation type in your schema.
  2. Implement the resolver function in your resolvers map.
  3. Return the modified object so the client can immediately update its cache.

Worked Example: Updating a Book Title

Imagine we are managing a list of books. We want to be able to rename a book. We'll start with a simple in-memory array to simulate our database.

1. Define the Schema

In your typeDefs, you must explicitly declare the Mutation type.

GraphQL
type Book {
  id: ID!
  title: String!
}

type Query {
  books: [Book]
}

type Mutation {
  updateBookTitle(id: ID!, newTitle: String!): Book
}

2. Implement the Resolver

Now, we create the logic. In our resolvers object, we add a Mutation key that mirrors the structure of our schema.

JAVASCRIPT
const books = [
  { id: CE9178">'1', title: CE9178">'The Great Gatsby' },
  { id: CE9178">'2', title: CE9178">'1984' }
];

const resolvers = {
  Query: {
    books: () => books,
  },
  Mutation: {
    updateBookTitle: (parent, args) => {
      const book = books.find(b => b.id === args.id);
      if (!book) return null; // Handle case where ID doesn't exist

      book.title = args.newTitle; // Perform the modification
      return book; // Return the updated object
    },
  },
};

By returning the book object, we allow the client to request the new title immediately without needing a separate follow-up query. This is a core pattern in Implementing Minimal Code: The Key to Simple, Clean Systems — keep your resolvers focused and your return values predictable.

Hands-on Exercise

Using the code above as your foundation, add a second mutation called addBook to your server.

  1. Update your typeDefs to include addBook(title: String!): Book.
  2. Implement the resolver to create a new object with a generated ID, push it into the books array, and return the new object.
  3. Test your implementation using Apollo Sandbox (as discussed in Using Apollo Sandbox: Testing Your Local GraphQL API).

Common Pitfalls

  • Forgetting to return the object: If your mutation returns null or undefined, the client cannot update its local cache. Always return the modified data.
  • Mutating the wrong reference: Ensure you are modifying the object within your data source, not just a local variable copy.
  • Ignoring errors: If the ID passed to the mutation doesn't exist, don't just return nothing. In a production system, you should throw an error (we will cover this in later lessons).

FAQ

Why must I return the modified object? Returning the object allows GraphQL clients (like Apollo Client) to automatically update the cache for that specific object. It keeps the UI consistent with the server state without requiring a full page refresh.

Can I perform multiple modifications in one mutation? Yes, but it is generally better to keep mutations granular. A single mutation should ideally represent a single "action" in your system to keep the API predictable.

How is this different from Filtering Data with Arguments? When we were Filtering Data with Arguments: Dynamic GraphQL Resolvers, we were only changing the view of the data. Mutations actually alter the underlying data structure.

Recap

We have successfully implemented a mutation that modifies our server's memory. We defined the operation in the schema, wrote the resolver to update the local array, and returned the object to complete the cycle. You now have the fundamental building blocks to move from a read-only API to a fully interactive one.

Up next: Learn how to clean up your arguments and improve type safety by using Input Types.

Similar Posts