GraphQL with .NET Core (Part - VI: Queries & Mutations)



Code samples used in this blog series have been updated to latest version of .NET Core (5.0.4) and GraphQL-Dotnet (4.2.0). Follow this link to get the updated samples.

Queries

We've come to the point, where you should have a good understanding of GraphQL Queries already. Few things you should keep in mind though,

  • A query should never change the value of a field. In other words, it should always fetch and never modify
  • Queries are executed in parallel. This is a huge improvement over REST APIs as you will see in the example below.

We can query for the store name and an item in parallel as follows,

query ($tag: String!){
  name
  item(tag: $tag){
    title
    price
  }
}
Query
{
  "data": {
    "name": "Steam",
    "item": {
      "title": "Diablo + Hellfire",
      "price": 9.99
    }
  }
}
Result

In REST, we would had to make two separate calls to achieve that scenario.

There are three types of operations you can do with GraphQL i.e. Query, Mutation and Subscription. It's good to specify an explicit name for whatever operation you are doing. It is very helpful for debugging and server-side logging. In general, we set the operation name after query, mutation or subscription keywords. For example,

query GameByTagQuery($tag: String!){
  item(tag: $tag){
    title
    price
  }
}

Mutations

We've been dealing with data fetching so far. But how do you cause side effects on the server-side data? GraphQL mutation is just the thing you are looking for here. Side effects can be anything ranging from a data insertion, patching, deletion or update

A mutation type also extends from ObjectGraphType. Following createItem field creates an item on the server side and returns it.

public class GameStoreMutation : ObjectGraphType
{
    public GameStoreMutation()
    {
        Field<ItemType>(
            "createItem",
            arguments: new QueryArguments(
                new QueryArgument<NonNullGraphType<ItemInputType>> { Name = "item" }
            ),
            resolve: context =>
            {
                var item = context.GetArgument<Item>("item");
                return new DataSource().AddItem(item);
            });
    }
}
GameStoreMutation.cs

Notice, we have a new ItemInputType as query argument. Previously, in Part-V, we had an example where we worked with a scaler type argument. But for passing a complex type as arguments, we have to work differently. Hence, come a new type i.e. InputObjectType. I've created a new ItemInputType and extend it from InputObjectGraphType,

public class ItemInputType : InputObjectGraphType
{
    public ItemInputType()
    {
        Name = "ItemInput";
        Field<NonNullGraphType<StringGraphType>>("tag");
        Field<NonNullGraphType<StringGraphType>>("title");
        Field<NonNullGraphType<DecimalGraphType>>("price");
    }
}
ItemInputType.cs

Following code is from DataStore; it uses a static list so that the newly added item stays on to the Items collection,

public class DataSource
{
	public static IList<Item> Items { get; set; }

	static DataSource()
	{
		Items = new List<Item>(){
			new Item { Tag= "cyberpunk_2077", Title="Cyberpunk 2077", Price=59.99M },
			new Item { Tag= "disco_elysium", Title="Disco Elysium", Price= 39.99M },
			new Item { Tag= "diablo", Title="Diablo + Hellfire", Price= 9.99M }
		};
	}

	public Item GetItemByTag(string tag)
	{
		return Items.First(i => i.Tag.Equals(tag));
	}

	public Item AddItem(Item item)
	{
		Items.Add(item);
		return item;
	}
}
DataSource.cs

Notice, we have returned the same item to the createItem field so that we can query nested fields of it. Why so? Because it is the preferred way.

Just like in queries, if the mutation field returns an object type, you can ask for nested fields. This can be useful for fetching the new state of an object after an update. - GraphQL Org.

Before we can run our application, a couple of DI registrations are needed for ItemInputType and InventoryMutation,

public void ConfigureServices(IServiceCollection services)
{
	/* Code removed for brevity */
    
    services.AddTransient<GameStoreMutation>();
    services.AddTransient<ItemInputType>();
    
    /* Code removed for brevity */
}
Startup.cs

Last but not least is, you have to register your schema with the newly created mutation object as following,

public class GameStoreSchema : Schema
{
    public GameStoreSchema(IServiceProvider serviceProvider) : base(serviceProvider)
    {
        Query = serviceProvider.GetRequiredService<GameStoreQuery>();
        Mutation = serviceProvider.GetRequiredService<GameStoreMutation>();
    }
}
GameStoreSchema.cs

Now, you can run a mutation within the in-browser IDE with a syntax like following,

mutation {
  createItem(item: {tag: "cnc", title: "Command & Conquer: Red Alert 3", price: 9.99}) {
    tag
    title
    price
  }
}

It will add the item passed within the item argument and return the tag, title and price of that newly added item,

You can also use variables to pass in the item argument via the Query Variables window,

Part VI

fiyazbinhasan/GraphQLCoreFromScratch
https://fiyazhasan.me/tag/graphql/. Contribute to fiyazbinhasan/GraphQLCoreFromScratch development by creating an account on GitHub.

GraphQL Mutaion

GraphQL Input Types