This post is going to continue my exploration of GraphQL using the .NET Boxed template as a jumping off point. The code I am starting with can be found here. Check out GraphQL using .NET Boxed: Queries from last week for an exploration of queries.
Mutations are GraphQL’s way of allowing a client to request changes to the data on the server-side.
Starting Point
As we discovered last week, MainSchema
is the central point to finding how GraphQL is set up in this template. Just for reference here is the full class.
public class MainSchema : Schema { public MainSchema( QueryObject query, MutationObject mutation, SubscriptionObject subscription, IDependencyResolver resolver) : base(resolver) { this.Query = resolver.Resolve<QueryObject>(); this.Mutation = mutation; this.Subscription = subscription; } }
We are interested in the Mutation
property which is being assigned a MutationObject
.
Mutation Object
The following is the full MutationObject
class which defines what mutations are allowed for which objects on this server and what happens when a mutation request is received.
public class MutationObject : ObjectGraphType<object> { public MutationObject(IHumanRepository humanRepository) { this.Name = "Mutation"; this.Description = "The mutation type for updates to our data."; this.FieldAsync<HumanObject, Human>( "createHuman", "Create a new human.", arguments: new QueryArguments( new QueryArgument<NonNullGraphType<HumanInputObject>>() { Name = "human", Description = "The human you want to create.", }), resolve: context => { var human = context.GetArgument<Human>("human"); return humanRepository.AddHuman(human, context.CancellationToken); }); } }
This is very similar to have the QueryObject
from last week was set up. The first big difference is in the QueryArguments
. The mutation is taking a HumanInputObject
class instead of an ID. If you look at the query argument you will also see that this argument isn’t allowed to be null.
new QueryArgument<NonNullGraphType<HumanInputObject>>()
What is the HumanInputObject
class? It is an InputObjectGraphType
and defines the shape of what the mutation query argument looks like. As you can see in the following it provides a name, description, and list of fields.
public class HumanInputObject : InputObjectGraphType { public HumanInputObject() { this.Name = "HumanInput"; this.Description = "A humanoid creature from Star Wars."; this.Field<NonNullGraphType<StringGraphType>>(nameof(Human.Name)); this.Field<StringGraphType>(nameof(Human.HomePlanet)); this.Field<ListGraphType<EpisodeEnumeration>>(nameof(Human.AppearsIn), "Which movie they appear in."); } }
Also, note that the fields are using nameof
on the properties of the Human
class to make sure the names match which will prevent any problems with the mapping between the 3 different human classes this project is working with. Here is an example of the field definition pulled out from the above sample.
this.Field<NonNullGraphType<StringGraphType>>(nameof(Human.Name));
Another thing to make note of is that even at the field level you can set if a field is allowed to be null or not on top of setting the type of the field.
Back over in the MutationObject
let’s look at the resolve
inside of the FieldAsync
call.
resolve: context => { var human = context.GetArgument<Human>("human"); return humanRepository.AddHuman(human, context.CancellationToken); });
This is pulling the human
query argument and it is being translated into an instance of the Human
class and then sent to the repository to be saved.
Wrapping Up
That covers the basic exploration of mutations. I’m thinking about looking at subscriptions.
The associated sample code can be found here.
Also published on Medium.