iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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! }

Test yourself

Q1. Mutations are intended for…
Q2. Mutation input usually uses…
Q3. Mutations run…

Discussion

Loading…