Mastering GraphQL Mutations with Hot Chocolate in .NET
Written on
Chapter 1: Understanding GraphQL Mutations
In this guide, we'll delve into the concept of mutations in GraphQL. While we have extensively discussed data retrieval, the mechanisms for modifying or removing data have yet to be addressed. Grasping mutations is vital for anyone looking to fully understand GraphQL's capabilities.
This article continues from our prior discussion. If you wish to follow along with the code, I highly suggest referring back to the previous piece. It's also accurate to think of mutations as part of CRUD operations.
Although mutations are structurally similar to queries, their primary function is to facilitate data modifications—be it creating, updating, or deleting records. Essentially, a mutation takes in data that is then processed, often involving database interactions.
Creating a New Todo
To start, we will create a file named Mutation.cs within the service directory. Since mutations need to process data, it's essential to establish the structure of the input.
Moreover, returning information from the creation endpoint can be advantageous for users who may not know the details of the newly created record, like its ID. Therefore, it's beneficial to return this information.
public record AddTodoInput(string Label, string Status);
public class AddTodoPayload
{
public AddTodoPayload(Todo todo)
{
Todo = todo;}
public Todo Todo { get; }
}
Next, let’s define the mutation itself. Here, the first parameter represents the incoming data, while the second is the database context, which is automatically provided.
Within the mutation, we will create a Todo object and save it into the database, ensuring to return the relevant data afterward.
public class Mutation
{
public async Task<AddTodoPayload> AddTodoAsync(AddTodoInput input, ApplicationDbContext db)
{
Todo todo = new()
{
Label = input.Label,
Status = input.Status,
};
db.Todos.Add(todo);
await db.SaveChangesAsync();
return new AddTodoPayload(todo);
}
}
Now, let's test the functionality. When we run the application, we will observe how the input is structured. First, we must type 'input' followed by the data.
Fantastic! We can now not only retrieve data but also create it. The next phase involves updating existing records.
Updating a Todo
Let’s proceed similarly for updates. Suppose we only want to allow changes to the status of a todo rather than modifying the entire record. With this requirement in mind, let’s implement the necessary modifications.
public record UpdateTodoInput(int Id, string Status);
public class UpdateTodoPayload
{
public UpdateTodoPayload(Todo? todo, bool updated)
{
Todo = todo;
Success = updated;
}
public Todo? Todo { get; }
public bool Success { get; }
}
It's important to note that to update specific entries, we need to verify how many are affected. A zero count may indicate an invalid ID. The frontend could submit either a valid or invalid ID, and we must handle both cases.
public async Task<UpdateTodoPayload> UpdateTodoAsync(UpdateTodoInput input, ApplicationDbContext db)
{
var todo = await db.Todos.FindAsync(input.Id);
var affectedRows = 0;
if (todo is not null)
{
todo.Status = input.Status;
affectedRows = await db.SaveChangesAsync();
}
return new UpdateTodoPayload(todo, affectedRows == 1);
}
Now, let’s verify that everything functions correctly. First, we will observe the current todos. At present, there are four items. We will proceed to update the todo with ID 4 to reflect the 'done' status.
The mutation will closely resemble the one used to create a new todo. From the response, we can confirm that the status has been updated to 'done'. To ensure accuracy, we can rerun the GetTodos query.
Deleting a Todo
As anticipated, the next step involves deletion. What input do we require? You guessed it—just the ID of the todo.
public record DeleteTodoInput(int Id);
public class DeleteTodoPayload
{
public DeleteTodoPayload(bool deleted)
{
Success = deleted;}
public bool Success { get; }
}
What’s our approach here? First, we need to locate the todo object. If we successfully find it, we can remove it from the database. Once again, the SaveChangesAsync method will indicate how many records were affected. If it returns 1, this confirms the frontend provided the correct ID, and the record exists.
public async Task<DeleteTodoPayload> DeleteTodoAsync(DeleteTodoInput input, ApplicationDbContext db)
{
var todo = await db.Todos.FindAsync(input.Id);
var affectedRows = 0;
if (todo is not null)
{
db.Todos.Remove(todo);
affectedRows = await db.SaveChangesAsync();
}
return new DeleteTodoPayload(affectedRows == 1);
}
Before proceeding with any deletions, let’s examine the current data. We will invoke the GetTodos query to check the existing records. As noted, the status was correctly modified in the previous step.
Now, we are ready to execute the mutation to delete the todo with ID 4. It’s done! To confirm the deletion, we can run the GetTodos query one last time. It works flawlessly. Excellent work!
If you found this article helpful and wish to join our growing community, please hit the follow button! Let's embark on this journey of knowledge together. Your thoughts and feedback are always appreciated, so feel free to share!
Stackademic
Thank you for reading to the end. Before you go, consider supporting the writer by clapping and following! 👏 Connect with us on X | LinkedIn | YouTube | Discord. Explore our other platforms: In Plain English | CoFeed | Venture.
Chapter 2: Video Resources
Learn how to add mutations to a Hot Chocolate 13 GraphQL API in this informative video.
Discover how to implement mutations in a .NET GraphQL API using Hot Chocolate in this detailed tutorial.