GraphQL, is described as a data query and manipulation language for APIs and a runtime for fulfilling queries with existing data. It allows varying clients to use your API and query for just the data they need, and it helps solve some issues that some REST services have, like over-fetching and under-fetching. In my article Intro to GraphQL for .NET Developers: Schema, Resolver, and Query Language, I wrote about the GraphQL type system, query language, schema, and resolver. I showed you how to build a GraphQL server using the GraphQL .NET library and tested the API with some queries from the GraphQL playground. In this article, I'll introduce you to GraphQL mutation. I'll also move off from using the static method I used in that article and use Entity Framework (with the in-memory provider) to access and store the data.
In that article, I used the GraphQL .NET library to build GraphQL server in ASP.NET Core. I'll be using the Hot Chocolate library in this article. Many GraphQL APIs that are built on .NET run using the GraphQL .NET currently, however, GraphQL .NET is no longer maintained and it's not advisable to continue using it for new applications. The Hot Chocolate library is in active development and releases often. Also, they have an active Slack workspace where you can ask questions. That's why I'll be using it in this article.
Project Set Up
You're going to create an ASP.NET Core application to host the GraphQL server. Open Visual Studio or JetBrains Rider and create a new ASP.NET Core project with the Empty
template, or open your command-line application and follow the instructions below if you use VS Code.
- Run the command dotnet new web GraphQL-Intro. You can replace GraphQL-Intro with your preferred project name.
- Run the command dotnet add package HotChocolate.AspNetCore to install Hot Chocolate ASP.NET Core integration NuGet package.
- Run the command dotnet add package HotChocolate.AspNetCore.Playground to add the GraphQL Playground package.
- Run the command dotnet add package Microsoft.EntityFrameworkCore.InMemory to add the in-memory provider for Entity Framework.
At this point, you have a basic ASP.NET Core project with the necessary dependencies needed to configure the GraphQL server and serve the API. The instructions use the dotnet CLI to add packages from NuGet. If you use Visual Studio and want to use the NuGet Package Manager console, run the commands listed in Figure 1 in the Package Manager Console.
Designing the Schema
You want to build an API that will allow users to query for books and their authors. You can imagine that you're building this API for a small publishing company. As you may already know, a GraphQL API is strongly typed, and as such, you define types to represent what data can be retrieved from the API. Because you're building an API about books and authors, you'll have Book
and Author
types in the schema. You'll be using the code-first approach to designing the schema, and for that, you'll define POCO (plain old CLR object) types that represent these types in the schema.
With your project open in your preferred text editor or IDE, create a new class file Book.cs
, and paste the code snippet below into it.
using HotChocolate;
public class Book
{
public int Id { get; set; }
[GraphQLNonNullType]
public string Title { get; set; }
public int Pages { get; set; }
public int Chapters { get; set; }
[GraphQLNonNullType]
public Author Author { get; set; }
}
Add a new class file Author.cs
and paste the code below into it.
using HotChocolate;
using HotChocolate.Types;
public class Author
{
[GraphQLType(typeof(NonNullType<IdType>))]
public int Id { get; set; }
[GraphQLNonNullType]
public string Name { get; set; }
}
In the code snippets you just added, you saw the attributes [GraphQLNonNullType]
and [GraphQLType]
. They're attributes from the Hot Chocolate library and you used it to specify the type for some fields. The [GraphQLNonNullType]
attribute is used to indicate that a field is non-nullable. For example, you used it to indicate that the title
field of the Book
type cannot be null. The [GraphQLType]
attribute is used to set the type for a field. In this code, it's used to specify that the Author Id
is a non-nullable GraphQL ID
type.
Mutation Operations
GraphQL mutation is a type of operation that allows clients to modify data on the server. It's through this operation type that you're able to add, remove, and update data on the server. To read data, you use GraphQL query operation type, which I covered in the article “Intro to GraphQL for .NET Developers: Schema, Resolver, and Query Language” (CODE Magazine, September 2019). GraphQL specifies three root types that define the operations of a GraphQL server:
- The Query type
- The Mutation type
- The Subscription type
The minimum a GraphQL server should define is the Query
type; the GraphQL engine you use validates the schema to ensure that this requirement is met.
You're going to add a mutation that allows users to add a new book to the application. This means that you need to define the Mutation
type.
Add a new file Mutation.cs
and paste the code from Listing 1 into it. The code in Listing 1 defines a class with a method named Book()
, which takes in some parameters. The function name will be used as the name for the field in the schema. The first method parameter is an Entity Framework DbContext object marked with the [Service]
attribute. The [Service]
attribute comes from the Hot Chocolate library. Hot Chocolate supports method injection and the [Service]
attribute is used to denote that the parameter should be injected. The rest of the parameters represent arguments for that field. The code in the method contains statements to save a new book to the database using Entity Framework.
Listing 1: Declaration for the Mutation type and its resolver
using System.Threading.Tasks;
using HotChocolate;
using HotChocolate.Types;
namespace GraphQL_Intro
{
public class Mutation
{
public async Task<Book> Book ([Service] BookDbContext dbContext, string title, int pages, string author, int chapters)
{
var book = new Book
{
Title = title,
Chapters = chapters,
Pages = pages,
Author = new Author { Name = author }
};
dbContext.Books.Add(book);
await dbContext.SaveChangesAsync();
return book;
}
}
}
Defining Query Operations
You've defined the Mutation type, which is the main focus of this article. However, you don't yet have a Query type. You'll define a Query type to allow users query for the saved books.
Add a new file Query.cs
, then copy and paste the code in Listing 2 into it. The code includes two methods GetBooks()
and GetBook()
. The methods are resolver functions and will be mapped to field books()
and book(ID id)
in the schema. By convention, Hot Chocolate removes the word “Get” from the method name and uses the remainder of the word for the field name. The BookDbContext
will be injected as a service and used to retrieve data.
Listing 2: Declaration for the Query type and its resolvers
using System.Collections.Generic;
using System.Linq;
using HotChocolate;
using Microsoft.EntityFrameworkCore;
public class Query
{
[GraphQLNonNullType]
public List<Book> GetBooks ([Service] BookDbContext dbContext) => dbContext.Books.Include(x => x.Author).ToList();
//By convention GetBook() will be declared book() in the query type.
public Book GetBook([Service] BookDbContext dbContext, int id) => dbContext.Books.FirstOrDefault(x => x.Id == id);
}
Configuring the GraphQL Middleware
So far, you've added code that defines the GraphQL schema and its resolvers. The next thing you're going to do is configure the GraphQL ASP.NET Core middleware. You'll also add the code for the BookDbContext
class that you've been using so far.
Add a new file BookDbContext.cs
that will contain code for the BookDbContext
class. Copy the code below and paste in the BookDbContext.cs file that was added following the instruction on this line/paragraph.
using Microsoft.EntityFrameworkCore;
public class BookDbContext : DbContext
{
public BookDbContext(DbContextOptions<BookDbContext> options) : base(options)
{
}
public DbSet<Book> Books { get; set; }
}
Open Startup.cs
and add the following Using statements to the file:
using HotChocolate;
using HotChocolate.AspNetCore;
using Microsoft.EntityFrameworkCore;
Go to the ConfigureServices()
method and add the code below to it:
services.AddDbContext<BookDbContext>(options => options.UseInMemoryDatabase(databaseName: "Books" ));
services.AddGraphQL(SchemaBuilder.New().AddQueryType<Query>().AddMutationType<Mutation>().Create());
This registers the GraphQL types with the GraphQL schema. It also registers the Entity Framework DbContext to use the in-memory database provider.
With the services now registered, you'll now add the GraphQL middleware to the pipeline so the server can serve GraphQL requests. Add the code below to the Configure()
method:
app.UseGraphQL("/graphql").UsePlayground("/graphql");
The method UseGraphQL()
configures the GraphQL middleware and sets the path for the GraphQL API to be /graphql
. You also set up the GraphQL Playground using the UsePlayground()
method.
At this point, the application is ready to handle GraphQL queries. You're going to test the application by running it and sending some queries through GraphQL Playground.
Test the Application
To test the application, you're going to start it and go to the GraphQL Playground. Open the command line and run the command dotnet run
or press F5
(or Ctrl+F5
) in Visual Studio or JetBrains Rider to start the application. Open your browser and go to https://localhost:5001/graphql/playground. This opens the GraphQL Playground UI, from which you'll write your GraphQL queries.
Click on the button labelled Schema
at the right side of the page and you should see the GraphQL schema displayed in SDL, just like it is in Figure 2. You'll notice a custom scalar type and directive declared in the schema. This is a bug in Hot Chocolate and will be fixed soon. It shows up because the directive you saw is automatically registered and that also brings in the custom scalar type with it.
Click on the Schema button again to close the window. Go to the text area on the left pane and paste in the query below in it. Click the Play button to run the mutation. This should give you a success response with the saved data returned as response, as you can see in Figure 3.
mutation {
book(title: "Shape Up", chapters: 4, pages: 100, author: "Anchor Duchito")
{
id
chapters
title
author {
name
}
}
}
You can modify the mutation query in order to add another book. Then copy and run the query below to get all the books:
{
books{
title
author {
name
}
}
}
This should give you a response similar to what you see in Figure 4.
Conclusion
I introduced you to GraphQL mutation, one of the three root operation types in GraphQL. You created a schema using the code-first approach. I showed you some of the attributes that can be used to configure the schema and also how to implement resolvers. You used the Entity Framework In-Memory database provider for your data source and you learned how to configure the GraphQL middleware.
You learned how to build a GraphQL API that has a mutation and query type. This should leave you with the skills to build a GraphQL API to perform CRUD operations. This would allow you brag among your friends that you're now a GraphQL developer. To prove that to them, I want you to add a new set of functionalities to the API as follows:
- Add a query to find authors by their name.
- Allow books to have publishers. This has you add a new type to the schema. You should be able to independently add publishers and query for all the books belonging to a publisher.
If you get stuck or want me to have a look at your solution, feel free to shoot me a DM on twitter. I'm @p_mbanugo on twitter.
Although this skill makes you a proud GraphQL developer, I'm not stopping here. In my next article, I'm going to teach you the data loader pattern and how this helps with the performance in a GraphQL application. Stay tuned and keep the coding spirit.
You can find the completed code for this post on GitHub at https://github.com/pmbanugo/graphql-intro-aspnet-core. Clone the repo and checkout the mutation branch.