Odyssey

Intro to GraphQL with .NET (C#) & Hot Chocolate
beta

Overview and setupWhat is GraphQL?Hot ChocolateHello worldApollo Sandbox ExplorerBuilding a schemaThe Query entry pointREST as a data sourceResolvers & data sourcesGraphQL argumentsA playlist's tracksResolver chainsMutation responseMutation input
13. Mutation response
3m

Overview

So far, we've only been working with one type of GraphQL operation: queries. These are read-only operations to retrieve data. To modify data, we need to use another type of GraphQL operation: mutations, which are write operations.

In this lesson, we will learn about common conventions for designing mutations and mutation responses

Mutations

On to the next feature in our MusicMatcher project: adding tracks to an existing playlist.

Let's take a look at the corresponding REST API method that enables this feature: POST /playlists/{playlist_id}/tracks.

https://spotify-demo-api-fe224840a08c.herokuapp.com/v1/docs

Mock REST API with POST endpoint

From the documentation, we need the following parameters:

  • playlist_id: The ID of the playlist, as a string (required)
  • position: An integer, zero-indexed, where we want to insert the track(s)
  • uris: A comma-separated string of uri values corresponding to the tracks we want to add

The method then returns an object with a snapshot_id property that represents the state of the playlist at that point in time.

All right, now how do we enable this functionality in GraphQL?

Designing mutations

Let's start with our schema.

For mutation names, we recommend starting with a verb that describes the specific action of our update operation (such as add, delete, or create), followed by whatever data the mutation acts on.

For the return type of a mutation, we could return the object type the mutation is acting on. However, we recommend following a consistent Response type for mutation responses.

Mutation responses

We'll need to account for any partial errors that might occur and return helpful information to the client. We recommend adding three common fields to all mutation responses:

  • code: an int that refers to the status of the response, similar to an HTTP status code.
  • success: a bool flag that indicates whether all the updates the mutation was responsible for succeeded.
  • message: a string to display information about the result of the mutation on the client side. This is particularly useful if the mutation was only partially successful and a generic error message can't tell the whole story.

Then, we'll also have a field for the object type we're mutating (Playlist, in our case). In certain mutations, this could be multiple objects!

As for naming conventions, return types usually end with the word Payload or Response.

The AddItemsToPlaylistPayload type

Following the best practices we covered, we'll name our mutation AddItemsToPlaylist. Let's first create the return type for this mutation: AddItemsToPlaylistPayload.

Under Types, we'll create a new class file called AddItemsToPlaylistPayload.

Types/AddItemsToPlaylistPayload.cs
namespace Odyssey.MusicMatcher;
public class AddItemsToPlaylistPayload
{
}

Then, we'll add the properties for code, success and message.

Types/AddItemsToPlaylistPayload.cs
[GraphQLDescription("Similar to HTTP status code, represents the status of the mutation.")]
public int Code { get; set; }
[GraphQLDescription("Indicates whether the mutation was successful.")]
public bool Success { get; set; }
[GraphQLDescription("Human-readable message for the UI.")]
public string Message { get; set; }

The object we're mutating in this case is a Playlist type, which is nullable because it's possible that something can go wrong in the mutation!

Types/AddItemsToPlaylistPayload.cs
[GraphQLDescription("The playlist that contains the newly added items.")]
public Playlist? Playlist { get; set; }

Lastly, we'll add the constructor. Remember to make the playlist parameter nullable! It's possible that the mutation could fail and we don't receive a playlist object.

Types/AddItemsToPlaylistPayload.cs
public AddItemsToPlaylistPayload(int code, bool success, string message, Playlist? playlist)
{
Code = code;
Success = success;
Message = message;
if (playlist != null) {
Playlist = playlist;
}
}

A new entry point: Mutation

Under Types, we'll create a new class file called Mutation.

Types/Mutation.cs
namespace Odyssey.MusicMatcher;
public class Mutation
{
}

Next, we'll write the resolver function for our AddItemsToPlaylist field. It will return the AddItemsToPlaylistPayload type. We'll add a description for it too.

Types/Mutation.cs
[GraphQLDescription("Add one or more items to a user's playlist.")]
public AddItemsToPlaylistPayload AddItemsToPlaylist()
{
}

Then, we'll hard-code the results of the resolver for now. Small steps! We'll create a new AddItemsToPlaylistPayload instance, passing in 200 for code, true for the success status, a successful message and a hard-coded Playlist object.

Types/Mutation.cs
return new AddItemsToPlaylistPayload(
200,
true,
"Successfully added items to playlist.",
new Playlist("6Fl8d6KF0O4V5kFdbzalfW", "Sweet Beats & Eats")
);

Our GraphQL server needs to know about this new entry point to the schema. Much like the Query type, we'll need to call AddMutationType on the GraphQL server and pass in the Mutation type.

Program.cs
builder.Services
.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.RegisterService<SpotifyService>();

Save all these changes and restart the server.

namespace Odyssey.MusicMatcher;
public class AddItemsToPlaylistPayload
{
[GraphQLDescription("Similar to HTTP status code, represents the status of the mutation.")]
public int Code { get; set; }
[GraphQLDescription("Indicates whether the mutation was successful.")]
public bool Success { get; set; }
[GraphQLDescription("Human-readable message for the UI.")]
public string Message { get; set; }
[GraphQLDescription("The playlist that contains the newly added items.")]
public Playlist? Playlist { get; set; }
public AddItemsToPlaylistPayload(int code, bool success, string message, Playlist? playlist)
{
Code = code;
Success = success;
Message = message;
if (playlist != null)
{
Playlist = playlist;
}
}
}
namespace Odyssey.MusicMatcher;
public class Mutation
{
public AddItemsToPlaylistPayload AddItemsToPlaylist()
{
return new AddItemsToPlaylistPayload(
200,
true,
"Added items to playlist successfully",
new Playlist("6Fl8d6KF0O4V5kFdbzalfW", "Sweet Beats & Eats")
);
}
}
using Odyssey.MusicMatcher;
using SpotifyWeb;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient<SpotifyService>();
builder
.Services
.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.RegisterService<SpotifyService>();
builder
.Services
.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder
.WithOrigins("https://studio.apollographql.com")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
var app = builder.Build();
app.UseCors();
app.MapGraphQL();
app.Run();

Explorer time!

Time to take our mutation for a spin! Back in Sandbox Explorer, we'll create a new workspace tab.

In the Documentation panel, let's navigate back to the root of our schema. Beside Query, we can see the Mutation type.

https://studio.apollographql.com/sandbox/explorer

Explorer - mutation added

Let's add the addItemsToPlaylist field and all the subfields inside it. For the playlist subfield, select the name for now.

GraphQL operation
mutation AddItemsToPlaylist {
addItemsToPlaylist {
code
message
success
playlist {
name
}
}
}

Run the mutation.

https://studio.apollographql.com/sandbox/explorer

Explorer - mutation response

Response data
{
"data": {
"addItemsToPlaylist": {
"code": "200",
"message": "Added items to playlist successfully",
"success": true,
"playlist": {
"name": "Sweet Beats & Eats"
}
}
}
}

Much like query responses, our mutation response followed the same shape as our mutation!

Practice

Which of these are good names for mutations based on the recommended conventions above?
In the mutation response type (AddItemsToPlaylistPayload), why is the modified object's return type (Playlist) nullable?

Key takeaways

  • Mutations are write operations used to modify data.
  • Naming mutations usually starts with a verb that describes the action, such as "add," "delete," or "create."
  • It's a common convention to create a consistent response type for mutation responses.

Up next

We'll learn best practices for mutation inputs and return real data.

Previous
Next

Share your questions and comments about this lesson

This course is currently in

beta
. Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.

You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              operation

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              mutations

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              mutations

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              operation

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              object type

              A type in a GraphQL schema that has one or more fields. User is an object type in the following example:

              type User {
              name: String!
              }
              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              fields

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              object type

              A type in a GraphQL schema that has one or more fields. User is an object type in the following example:

              type User {
              name: String!
              }
              mutations

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              GraphQL server

              A server that contains a GraphQL schema and can resolve client-requested operations that are executed against that schema.

              GraphQL server

              A server that contains a GraphQL schema and can resolve client-requested operations that are executed against that schema.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              Mutations

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              operations

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              mutations

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              NEW COURSE ALERT

              Introducing Apollo Connectors

              Connectors are the new and easy way to get started with GraphQL, using existing REST APIs.

              Say goodbye to GraphQL servers and resolvers—now, everything happens in the schema!

              Take the course