Mutations & Input Types
✏️ Mutations & Input Types
Query resolvers. Writing data uses the exact same resolver mechanics — the (parent, args, context, info) signature doesn't change — attached instead to fields on the Mutation root type from Chapter 2's schema.
Mutations Are Resolvers Too
The one real spec-level difference: the GraphQL specification guarantees that top-level mutation fields in a single request execute sequentially, one after another — unlike query fields, which are free to resolve in parallel. This matters when a mutation's side effects need to happen in a predictable order.
📥 Why Input Types Exist
A regular object type (like Chapter 2's User or Post) can never be used directly as an argument — GraphQL requires a separate, dedicated input type for structured arguments:
This isn't just a stylistic convention — a regular type's fields are backed by resolver functions and can reference other types that themselves have resolvers, which makes no sense for something the client is sending in as plain data. An input type is guaranteed to be pure, resolver-free data, which is exactly what an argument needs to be.
A Complete Mutation, Schema and Resolver
↩️ Returning Updated Data
Notice createPost returns Post!, not just a success boolean. This means the client can immediately query fields of the newly created post — including nested ones — in the same round trip as the write:
This is the same "everything in one round trip" idea from Chapter 1's under-fetching problem — just applied to writes instead of reads.
Naming Convention
verbNoun — createPost, updatePost, deletePost — makes a schema's write operations immediately scannable.
Payload Wrapper Pattern
Some schemas wrap results in a payload type — CreatePostPayload { post: Post, errors: [String!] } — rather than returning the raw type. Chapter 7 builds directly on this for structured error handling.
Separate Endpoints vs Named Operations
REST
POST /posts, PUT /posts/7, DELETE /posts/7 — the HTTP method and URL together identify the operation.
GraphQL
One endpoint, distinctly named mutation fields — and the client still chooses exactly which fields come back in the response.
💻 Coding Challenges
Challenge 1: Write an updatePost Mutation
Define an UpdatePostInput (with id: ID! and an optional title: String), add an updatePost field to Mutation returning Post!, and write its resolver.
Goal: Practice defining a second input type and mutation following the chapter's createPost pattern.
Challenge 2: Write a deletePost Mutation
Add a deletePost(id: ID!): Boolean! field to Mutation and write its resolver, then explain in one sentence why this particular mutation is one of the few reasonable cases for returning a plain Boolean instead of the deleted object.
Goal: Practice recognizing when the "return the updated object" convention doesn't apply.
Challenge 3: Explain a Schema Validation Error
A developer writes createPost(post: Post!): Post! instead of defining a separate input type, and the schema fails to build. Explain why GraphQL rejects using a regular type as an argument.
Goal: Practice explaining the input/output type distinction in your own words.
It's tempting to reach for an existing type (like Post) as a mutation argument instead of defining a near-duplicate input type — especially when the two look almost identical. GraphQL enforces this separation on purpose: a type's fields are meant to be resolved by functions and can reference other resolver-backed types, which is meaningless for something the client is handing in as plain data rather than something the server is computing and handing out. There's no way around defining the separate input type — the schema simply won't build if a regular type is used as an argument.
🎯 What's Next
Writing and reading are both covered — but Chapter 3's step-by-step resolver trace hinted at a real performance trap: The N+1 Problem & DataLoader, GraphQL's own version of the N+1 query problem, and how batching fixes it.