Mutations
Mutations are GraphQL operations that change server state. The shape mirrors queries: name, arguments, selection set. Convention: return the updated entity (and optionally errors) so clients can update their cache.
CRUD mutations + payloads + errors
EXAMPLE
# 1) Schema — input types + payload types
type Mutation {
createPost(input: NewPostInput!): CreatePostPayload!
updatePost(id: ID!, input: UpdatePostInput!): UpdatePostPayload!
deletePost(id: ID!): DeletePostPayload!
login(email: String!, password: String!): LoginPayload!
}
input NewPostInput {
title: String!
body: String!
tags: [String!]!
}
type CreatePostPayload {
post: Post
errors: [UserError!]!
}
type UserError {
field: [String!]!
message: String!
}
# 2) Run a mutation
mutation Create($input: NewPostInput!) {
createPost(input: $input) {
post {
id
title
slug
}
errors {
field
message
}
}
}
# variables:
# {
# "input": { "title": "Hi", "body": "first", "tags": ["intro"] }
# }
# 3) Apollo Server resolver
const resolvers = {
Mutation: {
async createPost(_, { input }, ctx) {
if (!ctx.user) throw new GraphQLError('Unauthenticated', { extensions: { code: 'UNAUTHENTICATED' } });
const parse = NewPostSchema.safeParse(input);
if (!parse.success) {
return {
post: null,
errors: parse.error.issues.map(i => ({ field: i.path.map(String), message: i.message })),
};
}
const post = await db.posts.create({ ...parse.data, authorId: ctx.user.id });
return { post, errors: [] };
},
},
};
# 4) Client (Apollo) — write to the cache on success
import { useMutation, gql } from '@apollo/client';
const CREATE_POST = gql`
mutation Create($input: NewPostInput!) {
createPost(input: $input) {
post { id title slug createdAt }
errors { field message }
}
}
`;
function NewPostForm() {
const [createPost, { loading }] = useMutation(CREATE_POST, {
update(cache, { data: { createPost } }) {
if (!createPost.post) return;
cache.modify({
fields: {
posts(existingRefs = []) {
const newRef = cache.writeFragment({
data: createPost.post,
fragment: gql`fragment NewPost on Post { id title slug createdAt }`,
});
return [newRef, ...existingRefs];
},
},
});
},
});
/* … */
}
# 5) Optimistic update — instant UI before the server replies
const [delPost] = useMutation(DELETE_POST, {
optimisticResponse: { deletePost: { __typename: 'DeletePostPayload', success: true } },
update(cache, _, { variables }) {
cache.evict({ id: cache.identify({ __typename: 'Post', id: variables.id }) });
cache.gc();
},
});
# 6) Mutation patterns
# • One mutation per intent (not one per entity)
# • Return the mutated entity so clients can update without a refetch
# • Return userError[] for expected failures (validation, business rules)
# • Throw / GraphQLError for unexpected failures (auth, server bug)
# • Idempotency: include a client-generated idempotency key for retries
# 7) Subscriptions complement mutations — push updates to OTHER clients
type Subscription {
postCreated: Post!
}
# After the createPost resolver:
pubsub.publish('postCreated', { postCreated: post });
Why it matters
Return { entity, errors } from every mutation. Expected user errors land in errors[]; unexpected ones throw. Clients distinguish form validation from infrastructure failure without checking HTTP status codes.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
mutation CreateUser($input: UserInput!) {
createUser(input: $input) { id name }
}
Try it Yourself »
Exercise
Top-level write entry point is…
type
{ createUser(input: UserInput!): User! }
Eight letters.
Discussion
Loading…