Working with Mutations – Changing Data with GraphQL
Welcome back! So far, you’ve learned how to fetch data using queries in GraphQL. You’ve designed a schema, written resolvers, and even sent your first requests to get data from your API. But APIs aren’t just about getting data. Often, you’ll need to add new data or update existing records. This is where mutations come into play.
Think of mutations as the opposite of queries. If queries are like asking for data, mutations are like telling the server, "Hey, I want to make some changes here." Whether you're adding a new user, updating a profile, or deleting something from the database, you’ll use mutations to handle these kinds of actions.
In this tutorial, we’ll focus on what mutations are, how to write your own, and how to ensure that the data you’re working with is valid. Let’s get started!
What’s a Mutation?
As mentioned earlier, mutations in GraphQL are similar to queries but with one key difference: they’re used to change data rather than just fetch it.
Think of a query as a "read" operation and a mutation as a "write" operation. You can think of it like this:
- Query: "Give me a list of users."
- Mutation: "Add a new user to the system."
You might already be familiar with the traditional CRUD operations in software development: Create, Read, Update, and Delete. In GraphQL, the Read operation is handled by queries, while Create, Update, and Delete are all done with mutations.
Let’s go through a real-world example. Imagine you have a user profile on a website. If you want to update your name or email, you’re essentially performing a mutation. You’re telling the server, "Hey, change my name to this new one," and the server processes that request and updates your information in the database.
So, let’s now see how to write one of these mutations.
Writing Your First Mutation
To make things simple, let’s create a mutation that allows us to add a new user to our system. We’ll define a new mutation in our GraphQL schema, write a resolver to handle that mutation, and then test it to make sure everything works.
1. Defining the Mutation in the Schema
First things first, we need to define the mutation in our GraphQL schema. In the same way we created the User type and the users query earlier, we’ll now create a mutation that adds a new user.
Open your index.js
file and update the typeDefs
with a new mutation:
const { ApolloServer, gql } = require('apollo-server');
// Define the GraphQL schema
const typeDefs = gql`
type User {
id: Int
name: String
age: Int
isActive: Boolean
}
type Query {
users: [User]
}
type Mutation {
addUser(name: String!, age: Int!, isActive: Boolean!): User
}
`;
Here’s what’s new:
- We’ve added a Mutation type with a single mutation called addUser.
- The addUser mutation takes three arguments: name, age, and isActive. Notice the ! after each type? That means these fields are required, so you can’t leave them out when calling the mutation.
- The mutation returns a User object, which will represent the newly created user.
2. Writing the Resolver for the Mutation
Next, we need to write a resolver that handles the logic for this mutation. When someone calls addUser, the resolver will be responsible for adding a new user to our list of users.
Let’s update the resolvers in index.js
:
const users = [
{ id: 1, name: 'John Doe', age: 25, isActive: true },
{ id: 2, name: 'Jane Smith', age: 30, isActive: false },
];
const resolvers = {
Query: {
users: () => users,
},
Mutation: {
addUser: (_, { name, age, isActive }) => {
const newUser = {
id: users.length + 1,
name,
age,
isActive,
};
users.push(newUser);
return newUser;
},
},
};
What’s happening here?
- We’ve added a Mutation object inside the resolvers. This tells Apollo Server how to handle the addUser mutation.
- The addUser resolver takes three arguments: name, age, and isActive. These values are passed from the client when the mutation is called.
- Inside the resolver, we create a new user object with the given data, add it to our list of users, and then return the newly created user.
3. Running the Mutation
Now that we’ve defined our mutation and written the resolver, it’s time to test it out.
- Start your Apollo Server by running:
node index.js
-
Open the GraphQL playground at
http://localhost:4000/
. -
Enter the following mutation:
mutation {
addUser(name: "Alice Johnson", age: 28, isActive: true) {
id
name
age
isActive
}
}
- Hit the “play” button, and you should see something like this:
{
"data": {
"addUser": {
"id": 3,
"name": "Alice Johnson",
"age": 28,
"isActive": true
}
}
}
Congratulations! You’ve just successfully added a new user to your system using a GraphQL mutation. The addUser mutation returned the new user object, including the id, name, age, and isActive fields.
Mutating Data: The Resolver’s Role
As we’ve seen, the resolver plays a critical role in handling mutations. Just like with queries, resolvers are responsible for fetching or modifying the data requested by the client. The difference with mutations is that they’re handling write operations—creating, updating, or deleting data.
In our example, the addUser resolver is responsible for:
- Taking the input from the client (in this case, the new user’s name, age, and status).
- Creating a new user object.
- Adding the new user to the list of existing users.
- Returning the newly created user to the client.
You can extend this example by adding more complex logic to the resolver. For instance, you could check if a user with the same name already exists before adding a new one. You could also connect your resolvers to a database instead of using a static list.
Validating Input Data
One important thing to keep in mind when working with mutations is that you don’t want to accept just any data from the client. For example, you wouldn’t want someone to create a user with an empty name or a negative age. This is where input validation comes in.
GraphQL makes it easy to validate input data by using required fields and custom validation logic in your resolvers. We’ve already seen how to make fields required by adding a ! to their type. But sometimes, you need more advanced validation.
Let’s say you want to make sure that the user’s age is between 1 and 120. You can add this logic directly inside the resolver:
const resolvers = {
Mutation: {
addUser: (_, { name, age, isActive }) => {
if (age < 1 || age > 120) {
throw new Error('Age must be between 1 and 120.');
}
const newUser = {
id: users.length + 1,
name,
age,
isActive,
};
users.push(newUser);
return newUser;
},
},
};
Now, if someone tries to add a user with an invalid age, they’ll get an error message, and the new user won’t be added.
Testing Mutations with Apollo Studio
While the GraphQL playground is great for quickly testing out queries and mutations, Apollo Studio offers a more comprehensive suite of tools for testing and monitoring your GraphQL API. Let’s take a quick look at how you can use Apollo Studio to test your mutations.
Setting Up Apollo Studio
- Head over to Apollo Studio at studio.apollographql.com.
- Sign up for a free account and create a new project.
- You can link your local Apollo Server instance by using the GraphQL endpoint (
http://localhost:4000/
).
Once your API is connected to Apollo Studio, you’ll have access to some cool features, like:
- A built-in query editor where you can test your queries and mutations.
- Performance tracking to see how long your resolvers take to execute.
- Error tracking to catch any issues that might come up in production.
To test your addUser mutation, simply open the query editor in Apollo Studio, paste in the mutation, and hit “run.” You’ll get the same results as in the GraphQL playground, but with more detailed insights into how your API is performing.
Wrapping Up
By the end of this tutorial, you’ve learned:
- What mutations are and how they differ from queries.
- How to write a mutation in your GraphQL schema.
- How to create a resolver to handle the mutation.
- The importance of input validation and how to add basic checks to your resolver.
- How to test your mutations using both the GraphQL playground and Apollo Studio.
Now that you’re comfortable with fetching and mutating data, you’re well on your way to mastering GraphQL. In the next tutorial, we’ll dive deeper into connecting your GraphQL API to a database, so you can start working with real data.
Until then, keep experimenting with mutations and see what else you can build!