From Query to Mutation with GraphQL.Conventions and ASP.NET Core
Introduction
In this post we will see how to create a GraphQL endpoint with GraphQL.Conventions and ASP.NET Core 2.1. We will cover creating a project from scratch until the point where we add our own queries and mutations.
In GraphQL, a query is a request for data, while a mutation is a request to change data. That change data could be anything from CRUD (Create, Update, Delete) to commands (CQRS anyone?). All types, queries and mutations are described in a so called schema.
What is GraphQL.Conventions?
GraphQL.Conventions is a library that sits on top of the GraphQL .NET library. The GraphQL.Conventions library takes away the need to create/maintain your own schema definition and generates the schema by convention.
The library is open-source, licensed under the MIT license and available on GitHub. A ready to use package is also available on NuGet.
Prerequisites
Before getting started, make sure you have the .NET Core 2.1 SDK installed on your system. If not, install .NET Core 2.1 SDK from this page.
To verify which version you have installed, run dotnet --version
, which should output something like 2.1.403
.
Creating a new project
Create a new folder and open a console window (terminal or cmd) into that folder and type the following command:
dotnet new webapi --name GraphApp
This command will scaffold a new ASP.NET Core Web API project, named GraphApp.csproj, into the GraphApp folder.
Before we continue, let’s start the application to verify all prerequisites are met by typing these commands in the console:
cd GraphApp
dotnet run
Now, open the browser and browse to https://localhost:5001/api/values, you should get back ["value1","value2"]
. Go back to the console and terminate the dotnet
process by pressing Ctrl+C.
Setting up GraphQL.Conventions
Adding the NuGet package
In the console, type:
dotnet add package GraphQL.Conventions
When using Visual Studio, you can install the package from the Package Manager Console like this:
PM> Install-Package GraphQL.Conventions
Preparing the query, mutation and user context object
Before actually adding queries and mutations, we will create three empty classes: Query, Mutation and UserContext, so we can continue with the rest of our setup.
Creating an implementation for IDependencyInjector
GraphQL.Conventions uses dependency injection (DI) but is not bound to a specific framework. This means we need to add a little implementation to glue the DI framework we’re using in our project. In our case, this is Microsoft.Extensions.DependencyInjection.
Configuring Startup.cs
Having all our (empty) classes in place, we have to register some required services by modifying the ConfigureServices
method in Startup.cs:
Creating the API controller
The API controller hosts our GraphQL endpoint at the route /api/graph
. This route accepts POST requests with a GraphQL formatted request in the body.
Testing the GraphQL endpoint
Testing a GraphQL endpoint is best done with a GraphQL client like GraphiQL (mind the i). For our tests, we have used the Electron based client, which can be downloaded from here. For more information about GraphiQL see the post by Clay Allsopp.
Once the client is installed, it’s time to start our application as usual:
dotnet run
Now, start the GraphiQL client and enter the GraphQL Endpoint URL of our application, which is: https://localhost:5001/api/graph. When expanding the Docs panel on the right, you should find documentation for an empty Query and empty Mutation object which perfectly reflects our empty Query and Mutation classes in code.
Writing our first mutation
In-memory repository
For demonstration purposes, we will add a model and in-memory repository for storing TODOs:
Register the repository in Startup.cs by adding this line to the ConfigureServices
method:
services.AddScoped<ITodoRepository, TodoRepository>();
Add a GraphQL todo type
In our repository, we have a TodoModel
type, but for GraphQL, we will add an additional Todo
type and map from one to the other. The reason for using a separate type for GraphQL is because we want to annotate the class and its properties with GraphQL.Conventions specific attributes, like [Description]
.
Mapping a database entity or service model to the GraphQL model can be done manually or by using a framework like AutoMapper.
Adapt the mutation class
Open Mutation.cs and add a method for adding TODOs to the in-memory repository:
The AddTodo
method itself takes two parameters: ITodoRepository repository
will be injected by GraphQL.Conventions because of the [Inject]
attribute, while NonNull<string> description
will become a mutation parameter. NonNull<>
is a decorator, telling GraphQL.Conventions that this field is required.
In our example, the mutation input parameter is a string. This can as well be a class, in which case the class must be decorated with the [InputType]
attribute. A class marked with [InputType]
can be used as a query or mutation input type, not as a return/result type.
Testing the AddTodo mutation
Start the application and go to the GraphiQL client. In the GraphiQL client, press Ctrl+R to reload the schema. When we now check the mutation in the documentation, we should see the addTodo
mutation being mentioned.
In the left window of GraphiQL, add a test mutation to add a TODO item:
mutation AddTodo {
addTodo(description: "Do the dishes") {
id
description
}
}
Execute this mutation by clicking the Execute Query ( ▶) button. If all goes well, a response like this should appear in the output window:
{
"data": {
"addTodo": {
"id": "78081912-ec8e-4486-8326-3c23bb3c8fad",
"description": "Do the dishes"
}
}
}
That’s it. We have added a TODO in our in-memory repository by executing a GraphQL mutation!
Writing our first query
In the previous step, we’ve learned how to add TODOs to the in-memory repository. In this step, we will create a query to get all TODOs at once and a query to only get a single TODO.
Adapt the query class
Open the Query.cs and add the methods for querying the data:
Testing the queries
Again, start the application and go to the GraphiQL client. In the GraphiQL client, press Ctrl+R to reload the schema. When we now check the query in the documentation, we should see the todo
and todos
queries being mentioned.
In the left window of GraphiQL, add two additional queries:
query All {
todos {
id
description
}
}query One {
todo(id: "abdbb6f9-ff50-4ea0-8c1b-24a4066b40ae") {
id
description
}
}
Execute the All query by clicking the Execute Query ( ▶) button and selecting All. A response like this should appear in the output window:
{
"data": {
"todos": []
}
}
The empty array means no TODOs were added to the in-memory repository. Add some by using the mutation created in the previous step and run this query again.
Voila! That’s all there is to it.
Wrapping up
In this post we have seen how to create our own GraphQL endpoint and how to add queries and mutations with documented type definitions.
The complete source code of the project created in this post can be found here.