Marketplace

graphql-mutations

GraphQL mutation design including payload patterns, field-specific errors, input objects, and HTTP semantics. Use when designing or implementing GraphQL mutations.

$ 安裝

git clone https://github.com/jovermier/cc-stack-marketplace /tmp/cc-stack-marketplace && cp -r /tmp/cc-stack-marketplace/plugins/cc-graphql/skills/graphql-mutations ~/.claude/skills/cc-stack-marketplace

// tip: Run this command in your terminal to install the skill


name: graphql-mutations description: GraphQL mutation design including payload patterns, field-specific errors, input objects, and HTTP semantics. Use when designing or implementing GraphQL mutations.

GraphQL Mutations

Expert guidance for designing effective GraphQL mutations.

Quick Reference

PatternUse WhenStructure
Result payloadAll mutationsmutationName(input): MutationNamePayload!
Field-specific errorsValidation failureserrors: [FieldError!]! in payload
Input objectsComplex argumentsinput: MutationNameInput!
Noun + Verb namingState changescreateUser, deletePost, closeCard
Idempotent mutationsSafe retriesDesign for repeatable calls
Optimistic UIClient-side updatesReturn predicted result

What Do You Need?

  1. Payload design - Return types, error handling
  2. Input objects - Structuring mutation arguments
  3. Error patterns - Field-specific vs top-level errors
  4. Naming - Mutation naming conventions
  5. Side effects - Handling async operations

Specify a number or describe your mutation scenario.

Routing

ResponseReference to Read
1, "payload", "return", "response"payloads.md
2, "input", "argument", "parameter"inputs.md
3, "error", "validation", "field error"errors.md
4, "naming", "convention"naming.md
5, general mutationsRead relevant references

Critical Rules

  • Always return a payload: Never just a boolean or the object
  • Use input objects for complex arguments: Don't use many scalars
  • Field-specific errors in response: Let clients handle per-field failures
  • Noun + verb naming: createUser, deleteUser, not user
  • Mutations are POST-only: Never use GET for mutations
  • Design for idempotency: Safe to call multiple times

Mutation Template

# Input object for complex arguments
input CreateUserInput {
    name: String!
    email: String!
    password: String!
}

# Payload with result and errors
type CreateUserPayload {
    user: User
    errors: [UserError!]!
}

# Field-specific error type
type UserError {
    field: [String!]!  # Path to field: ["email"] or ["user", "emails", 0]
    message: String!
}

# Mutation definition
type Mutation {
    """
    Creates a new user account
    """
    createUser(input: CreateUserInput!): CreateUserPayload!
}

Mutation Implementation

// Good: Mutation with proper payload and field errors
func (r *mutationResolver) CreateUser(ctx context.Context, input CreateUserInput) (*CreateUserPayload, error) {
    // Validate
    var errs []UserError
    if input.Name == "" {
        errs = append(errs, UserError{
            Field:   []string{"name"},
            Message: "Name is required",
        })
    }
    if !isValidEmail(input.Email) {
        errs = append(errs, UserError{
            Field:   []string{"email"},
            Message: "Invalid email format",
        })
    }
    if len(errs) > 0 {
        return &CreateUserPayload{Errors: errs}, nil
    }

    // Create
    user, err := r.db.CreateUser(input)
    if err != nil {
        if errors.Is(err, db.ErrDuplicate) {
            return &CreateUserPayload{
                Errors: []UserError{{
                    Field:   []string{"email"},
                    Message: "Email already exists",
                }},
            }, nil
        }
        return nil, fmt.Errorf("failed to create user")
    }

    return &CreateUserPayload{User: user, Errors: []UserError{}}, nil
}

Common Mutation Patterns

Create

type Mutation {
    createUser(input: CreateUserInput!): CreateUserPayload!
}

type CreateUserPayload {
    user: User
    errors: [UserError!]!
}

Update

type Mutation {
    updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
}

type UpdateUserPayload {
    user: User
    errors: [UserError!]!
}

Delete

type Mutation {
    deleteUser(id: ID!): DeleteUserPayload!
}

type DeleteUserPayload {
    deletedUserId: ID
    errors: [UserError!]!
}

State Change (Noun + Verb)

type Mutation {
    """
    Closes a card (marks as closed, not deleted)
    """
    closeCard(id: ID!): CloseCardPayload!
}

type CloseCardPayload {
    card: Card
    errors: [UserError!]!
}

Error Handling Patterns

Error TypeResponse Pattern
Validation errorsReturn in payload errors field
Duplicate unique keyReturn in payload errors field
Not foundReturn in payload errors field
Permission deniedReturn in payload errors field
Internal server errorReturn nil, wrap error (don't expose)

HTTP Semantics

ConcernGuidance
HTTP methodAlways POST for mutations
CachingMutations are never cached
IdempotencyDesign mutations to be safely repeatable
Side effectsDocument non-obvious side effects
Async operationsReturn payload with job ID, query for status

Common Mutation Mistakes

MistakeSeverityFix
Returning just booleanMediumUse payload with result
No field-specific errorsHighAdd errors array to payload
Too many scalar argumentsMediumUse input object
Verb + noun namingLowUse noun + verb (createUser)
Using GET for mutationsCriticalAlways use POST
No validation errors in payloadHighReturn validation failures

Reference Index

FileTopics
payloads.mdResult types, error patterns, response structure
inputs.mdInput objects, nested inputs, validation
errors.mdField errors, error types, client handling
naming.mdConventions, verb selection, consistency

Success Criteria

Mutations are well-designed when:

  • All mutations return a payload type
  • Field-specific errors returned in payload
  • Input objects used for complex arguments
  • Noun + verb naming (createUser, deletePost)
  • POST only (never GET)
  • Idempotent where possible
  • Validation errors returned, not thrown
  • No internal errors exposed to clients