Update Mutation
Learn how to build update mutations to modify existing records, handle partial updates, and manage edit workflows.
Update Mutation
In the previous episode, we learned how to add and delete data. Now let's complete the CRUD picture with update mutations — modifying existing records.
Defining the Update Schema
For updates, we want to allow partial updates — only changing the fields that are provided. We create an input type where all fields are optional:
type Mutation {
addGame(game: AddGameInput!): Game
deleteGame(id: ID!): [Game]
updateGame(id: ID!, edits: EditGameInput!): Game
}
input AddGameInput {
title: String!
platform: [String!]!
}
input EditGameInput {
title: String # Optional — only update if provided
platform: [String!] # Optional — only update if provided
}
Notice the difference:
| Input Type | Fields | Purpose |
|---|---|---|
AddGameInput | All required (!) | Creating new records |
EditGameInput | All optional (no !) | Partial updates |
Writing the Update Resolver
const resolvers = {
Mutation: {
// ... other mutations ...
updateGame(_, args) {
// Find the game to update
const gameIndex = games.findIndex(g => g.id === args.id);
if (gameIndex === -1) {
throw new Error('Game not found');
}
// Merge existing data with provided edits
games[gameIndex] = {
...games[gameIndex], // Keep existing fields
...args.edits, // Override with provided edits
};
return games[gameIndex];
},
},
};
How Spread Merging Works
The magic is in the spread operator. If a user only provides a title:
// Existing game
{ id: "1", title: "Zelda", platform: ["Switch"] }
// args.edits = { title: "Zelda: TotK" }
// After spread merge:
{
...existingGame, // id: "1", title: "Zelda", platform: ["Switch"]
...args.edits, // title: "Zelda: TotK"
}
// Result: { id: "1", title: "Zelda: TotK", platform: ["Switch"] }
// Only the title changed! Platform stayed the same.
Running the Update Mutation
Update just the title:
mutation UpdateGame($id: ID!, $edits: EditGameInput!) {
updateGame(id: $id, edits: $edits) {
id
title
platform
}
}
{
"id": "1",
"edits": {
"title": "Zelda: Tears of the Kingdom"
}
}
Update just the platform:
{
"id": "2",
"edits": {
"platform": ["PS5", "PC", "Xbox"]
}
}
Update both fields:
{
"id": "3",
"edits": {
"title": "Elden Ring: Shadow of the Erdtree",
"platform": ["PS5", "Xbox", "PC"]
}
}
Complete Mutations for All Types
type Mutation {
# Game mutations
addGame(game: AddGameInput!): Game
updateGame(id: ID!, edits: EditGameInput!): Game
deleteGame(id: ID!): [Game]
# Review mutations
addReview(review: AddReviewInput!): Review
updateReview(id: ID!, edits: EditReviewInput!): Review
deleteReview(id: ID!): [Review]
}
input AddGameInput {
title: String!
platform: [String!]!
}
input EditGameInput {
title: String
platform: [String!]
}
input AddReviewInput {
rating: Int!
content: String!
game_id: ID!
author_id: ID!
}
input EditReviewInput {
rating: Int
content: String
}
Full Resolver Implementation
const resolvers = {
Query: {
games: () => games,
game: (_, args) => games.find(g => g.id === args.id),
reviews: () => reviews,
review: (_, args) => reviews.find(r => r.id === args.id),
authors: () => authors,
author: (_, args) => authors.find(a => a.id === args.id),
},
Game: {
reviews: (parent) => reviews.filter(r => r.game_id === parent.id),
},
Review: {
game: (parent) => games.find(g => g.id === parent.game_id),
author: (parent) => authors.find(a => a.id === parent.author_id),
},
Author: {
reviews: (parent) => reviews.filter(r => r.author_id === parent.id),
},
Mutation: {
addGame: (_, args) => {
const game = { id: String(games.length + 1), ...args.game };
games.push(game);
return game;
},
updateGame: (_, args) => {
const idx = games.findIndex(g => g.id === args.id);
if (idx === -1) throw new Error("Game not found");
games[idx] = { ...games[idx], ...args.edits };
return games[idx];
},
deleteGame: (_, args) => {
games = games.filter(g => g.id !== args.id);
return games;
},
addReview: (_, args) => {
const review = { id: String(reviews.length + 1), ...args.review };
reviews.push(review);
return review;
},
updateReview: (_, args) => {
const idx = reviews.findIndex(r => r.id === args.id);
if (idx === -1) throw new Error("Review not found");
reviews[idx] = { ...reviews[idx], ...args.edits };
return reviews[idx];
},
deleteReview: (_, args) => {
reviews = reviews.filter(r => r.id !== args.id);
return reviews;
},
},
};
Course Summary 🎉
Congratulations! You've completed the GraphQL Crash Course! Here's everything you've learned:
- What GraphQL is — a query language that solves REST's problems
- Query basics — fields, nested queries, arguments
- Apollo Server — building a GraphQL server from scratch
- Schema & types — SDL, scalars, objects, enums
- Resolvers — connecting schema to data
- Query variables — dynamic, reusable queries
- Related data — modeling and resolving relationships
- Mutations — creating and deleting data
- Update mutations — partial updates with edit input types
Where to Go Next
- Subscriptions — real-time data with WebSockets
- Apollo Client — using GraphQL in React/Vue/Angular
- DataLoader — solving the N+1 query problem
- Authentication — securing your GraphQL API
- Database integration — connecting to PostgreSQL, MongoDB, etc.
Happy coding! 🚀