Episode 5 covers mutations for writing data: basic structure and syntax, designing good input types, complete CRUD operations, response patterns with payloads, serial execution for multiple mutations, and best practices for naming and idempotency.

Queries let you read data; mutations let you change it. Episode 5 dissects mutations completely: syntax structure, input type design, create-update-delete operations, and consistent response patterns.
We'll also cover the important behavior of serial execution for multiple mutations, plus best practices like naming, idempotency, and input-level validation. After this episode, you'll be able to design a safe, easy-to-use write layer for your GraphQL API.
Mutations are written with the mutation keyword, followed by the operation name, and the field selection that forms the result:
mutation BuatPost($input: CreatePostInput!) {
createPost(input: $input) {
id
title
createdAt
}
}With the variables sent separately as a JSON object {"input": {...}}:
{
"input": {
"title": "Belajar GraphQL",
"body": "Isi postingan pertama"
}
}Key difference from queries: mutations change state and execute serially. GraphQL guarantees mutation fields run one at a time, in sequence, so the second mutation in one request sees the effect of the first.
Always request the mutation's result data in the response, not just a status. By returning the mutated object (like id, title, createdAt above), the client doesn't need an additional query to get fresh data — eliminating an extra round-trip.
An input type is how GraphQL accepts structured data for mutations. The design principle: move structure validation to the input level, so resolvers don't have to guess at the data shape:
input CreatePostInput {
title: String!
body: String!
tags: [String!]
publishedAt: DateTime
}Use specific types, mark required fields with !, and separate inputs for create and update as in episode 3. Don't use object types to receive data — they weren't designed for that.
Inputs used by many mutations (for example, addresses or categories) can become a single reusable input type. This keeps things consistent and reduces duplication. Note that input types can be nested — an input can contain another input — for hierarchical data like order items.
The common pattern for each entity is four mutations:
type Mutation {
createPost(input: CreatePostInput!): Post
updatePost(id: ID!, input: UpdatePostInput!): Post
deletePost(id: ID!): Post
}For updates there are two approaches. Full update requires sending every field (good for complete forms). Partial update uses an input with all nullable fields, like UpdatePostInput with title: String and body: String, so only the fields provided are changed — this is the most common pattern and is PATCH-style.
To create or update many entities at once, use a list-typed input:
type Mutation {
createPosts(input: [CreatePostInput!]!): [Post]
}This pattern reduces the number of requests compared to calling one mutation at a time. Note the trade-offs: large batches take longer to process and error handling becomes more complex, because some operations can succeed while others fail.
Modern best practice is to return a payload object that wraps the result, instead of returning the object directly:
type CreatePostResult {
post: Post
errors: [FieldError]
}
type FieldError {
field: String!
message: String!
}With a payload, you can return successful data and per-field errors in a single structure. This is the foundation of a very powerful pattern: result patterns with union types, which we'll cover thoroughly in episode 11. For early simplicity, you can start by returning the object directly, then move to payloads once your API gets complex.
On the client side, an optimistic response lets the UI update immediately before the server responds, then syncs when the response arrives. This isn't part of the schema — it's a client feature — and we'll implement it with Apollo Client in episode 25.
createX, updateX, deleteX, addTagToPost.createPost, not submit.clientMutationId or an idempotency key.type Mutation {
createOrder(input: CreateOrderInput!, idempotencyKey: String!): Order
}Input-level validation (episode 10) and consistent error handling (episode 11) are two things every production mutation must have.
Key takeaways:
In the next episode, episode 6, you'll learn about resolvers — the heart of GraphQL — from the anatomy of a resolver function, the four parameters parent, args, context, and info, resolver chains, the context object, to async resolver patterns and resolver file organization. This is where data is actually fetched!