While queries fetch data in GraphQL, mutations are how you modify it. Think of mutations as the POST, PUT, PATCH, and DELETE operations of REST APIs, but more flexible and type-safe.
This comprehensive guide covers everything from basic CRUD operations to advanced patterns like optimistic updates and batch mutations. Use our GraphQL formatter to beautify your mutations and schema validator to check your schemas. For more examples, check our GraphQL examples guide.
What Are Mutations?
Mutations are GraphQL operations that change data on the server. Unlike queries which are read-only, mutations:
- •Create new records in your database
- •Update existing data
- •Delete records
- •Execute side effects like sending emails or processing payments
💡 Key Difference: Mutations are executed sequentially (one after another), while queries run in parallel. This ensures data consistency when multiple mutations are sent together.
Mutation Syntax Basics
Schema Definition
First, define your mutation in the schema:
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String): User!
deleteUser(id: ID!): Boolean!
}Mutation Query
Then call it from your client:
mutation {
createUser(name: "John Doe", email: "[email protected]") {
id
name
email
createdAt
}
}CRUD Operations with Mutations
CCreate - Adding New Data
Schema Definition
input CreateProductInput {
name: String!
description: String
price: Float!
categoryId: ID!
tags: [String!]
}
type Mutation {
createProduct(input: CreateProductInput!): Product!
}Mutation with Variables
Mutation:
mutation CreateProduct(
$input: CreateProductInput!
) {
createProduct(input: $input) {
id
name
price
createdAt
}
}Variables:
{
"input": {
"name": "Laptop",
"description": "...",
"price": 999.99,
"categoryId": "tech",
"tags": ["electronics"]
}
}Best Practice: Use input types to group related fields and keep mutations clean.
RRead - Use Queries (Not Mutations)
For reading data, always use queries, not mutations. Queries can run in parallel and are cacheable. See our GraphQL tutorial for query examples.
UUpdate - Modifying Existing Data
Partial Updates
input UpdateProductInput {
name: String
description: String
price: Float
tags: [String!]
}
type Mutation {
updateProduct(
id: ID!
input: UpdateProductInput!
): Product!
}
# Usage
mutation UpdateProduct {
updateProduct(
id: "123"
input: {
price: 899.99
name: "Gaming Laptop"
}
) {
id
name
price
updatedAt
}
}Tip: Make update input fields optional so clients can update only what changed.
DDelete - Removing Data
Simple Delete
type Mutation {
deleteProduct(id: ID!): Boolean!
}
mutation DeleteProduct($id: ID!) {
deleteProduct(id: $id)
}Delete with Confirmation Response
type DeleteResponse {
success: Boolean!
message: String
deletedId: ID
}
type Mutation {
deleteProduct(id: ID!): DeleteResponse!
}
mutation DeleteProduct {
deleteProduct(id: "123") {
success
message
deletedId
}
}Security: Always implement authorization checks in your resolvers before deleting data!
Advanced Mutation Patterns
1. Batch Mutations
Create or update multiple items at once:
type Mutation {
createProducts(inputs: [CreateProductInput!]!): [Product!]!
updateProducts(updates: [UpdateProductBatch!]!): [Product!]!
}
input UpdateProductBatch {
id: ID!
input: UpdateProductInput!
}
mutation CreateMultiple {
createProducts(inputs: [
{ name: "Laptop", price: 999 },
{ name: "Mouse", price: 29 },
{ name: "Keyboard", price: 79 }
]) {
id
name
}
}2. Nested Mutations
Create related data in a single mutation:
input CreateOrderInput {
userId: ID!
items: [OrderItemInput!]!
shippingAddress: AddressInput!
}
input OrderItemInput {
productId: ID!
quantity: Int!
}
input AddressInput {
street: String!
city: String!
zipCode: String!
}
mutation CreateOrder {
createOrder(input: {
userId: "user123"
items: [
{ productId: "prod1", quantity: 2 }
{ productId: "prod2", quantity: 1 }
]
shippingAddress: {
street: "123 Main St"
city: "New York"
zipCode: "10001"
}
}) {
id
total
items {
product { name }
quantity
}
}
}3. Optimistic Response Pattern
Return the expected result immediately for better UX:
// Client-side (Apollo Client example)
const [likePost] = useMutation(LIKE_POST, {
optimisticResponse: {
likePost: {
__typename: "Post",
id: postId,
likes: currentLikes + 1,
likedByMe: true
}
}
});
// UI updates instantly, then syncs with server4. Upsert Pattern
Create if doesn't exist, update if it does:
type Mutation {
upsertProduct(
id: ID
input: ProductInput!
): Product!
}
mutation UpsertProduct {
upsertProduct(
id: "123" # Optional: omit to create new
input: {
name: "Updated Laptop"
price: 899.99
}
) {
id
name
price
}
}Error Handling in Mutations
Response Union Pattern
Return either success or error types:
type CreateProductSuccess {
product: Product!
}
type ValidationError {
field: String!
message: String!
}
type CreateProductError {
errors: [ValidationError!]!
}
union CreateProductResult =
CreateProductSuccess | CreateProductError
type Mutation {
createProduct(
input: CreateProductInput!
): CreateProductResult!
}
mutation CreateProduct($input: CreateProductInput!) {
createProduct(input: $input) {
... on CreateProductSuccess {
product {
id
name
}
}
... on CreateProductError {
errors {
field
message
}
}
}
}Standard Error Response
type MutationResponse {
success: Boolean!
message: String
errors: [Error!]
data: Product
}
type Error {
field: String
message: String!
code: String
}
mutation CreateProduct {
createProduct(input: {...}) {
success
message
errors {
field
message
code
}
data {
id
name
}
}
}Mutation Best Practices
✅ Return Updated Data
Always return the modified data so clients can update their cache.
✅ Use Input Types
Group related arguments into input types for cleaner APIs.
✅ Make Fields Nullable
In update mutations, make fields optional so partial updates work.
✅ Validate Input
Check data validity in resolvers before modifying the database.
✅ Handle Errors Gracefully
Return user-friendly error messages, not internal errors.
✅ Use Transactions
Wrap related database operations in transactions for consistency.
❌ Don't Use for Reads
Mutations are for writes only. Use queries for reading data.
❌ Avoid Generic Mutations
Create specific mutations instead of one "update" for everything.
Complete Example: User Registration
Here's a complete user registration mutation with validation, error handling, and best practices:
Schema Definition
input RegisterUserInput {
email: String!
password: String!
name: String!
}
type RegisterUserSuccess {
user: User!
token: String!
}
type RegisterUserError {
errors: [ValidationError!]!
}
union RegisterUserResult =
RegisterUserSuccess | RegisterUserError
type Mutation {
registerUser(
input: RegisterUserInput!
): RegisterUserResult!
}Resolver Implementation
const resolvers = {
Mutation: {
registerUser: async (parent, { input }, context) => {
// Validate input
const errors = [];
if (input.password.length < 8) {
errors.push({
field: 'password',
message: 'Password must be at least 8 characters'
});
}
// Check if email exists
const existingUser = await db.users.findByEmail(input.email);
if (existingUser) {
errors.push({
field: 'email',
message: 'Email already registered'
});
}
if (errors.length > 0) {
return {
__typename: 'RegisterUserError',
errors
};
}
// Hash password
const hashedPassword = await bcrypt.hash(input.password, 10);
// Create user
const user = await db.users.create({
email: input.email,
password: hashedPassword,
name: input.name
});
// Generate JWT token
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET
);
return {
__typename: 'RegisterUserSuccess',
user,
token
};
}
}
};Client Usage
mutation RegisterUser($input: RegisterUserInput!) {
registerUser(input: $input) {
... on RegisterUserSuccess {
user {
id
email
name
}
token
}
... on RegisterUserError {
errors {
field
message
}
}
}
}
# Variables
{
"input": {
"email": "[email protected]",
"password": "securepass123",
"name": "John Doe"
}
}GraphQL Development Tools
GraphQL Formatter
Format mutations beautifully
Schema Validator
Validate mutation schemas
GraphQL to TypeScript
Generate types for mutations
JSON to GraphQL
Generate schemas from JSON
GraphQL Minifier
Compress mutations
GraphQL to JSON
Convert to JSON format
GraphQL to JSON Schema
Generate JSON Schema
JSON Formatter
Format JSON responses
JSON Validator
Validate JSON data
Related GraphQL Guides
External Resources
Official Documentation
- •GraphQL.org Mutations - Official mutation documentation
- •Apollo Mutations Guide - Client-side mutation patterns
- •Apollo Server Mutations - Server-side implementation
Best Practices
- •GraphQL Rules - Community best practices
- •Error Handling - Handling errors in GraphQL