In this article we will be learning about GraphQL with Asp.Net Core 5, we will be building a Todo List functionality
You can watch the full video on Youtube
And you can get the source from GitHub using this link:
https://github.com/mohamadlawand087/v28-Net5-GraphQL
So what we will cover today:
- The Problem we want to solve
- What is GraphQL
- REST vs GraphQL
- Core Concepts
- Ingredients
- Coding
As always you will find the source code in the description down below. Please like, share and subscribe if you like the video. It will really help the channel
The Problem and the Solution
in order to understand graphQL let us understand the problem that it solves.
It was originally built by facebook to solve their data fetching needs. back in 2012 when facebook released their application it had a lot of criticism about the performance, the lag, the battery drainage as it was doing a lot of API calls to fetch the users data
To solve this facebook introduced GraphQL which turns all of these request into a single request. With that single API endpoint we can retrieve any data from the server using the GraphQL query language. Where we tell the API exactly the information we want and the API will return it, this will solve for a bunch of the problems.
So let us analyse the query we have, we are actually nesting objects inside the same request we are requesting more information through the nested obj. We get back the result in json, so in Rest API it could have been multiple requests to get this information while in GraphQL it was a single call.
We are taking what was a different requests and instead making the client the responsible for figuring out the logic of which the data needs to be processed to get the information we are delegating all of these requests to the server and requesting the server to handle the information binding based on the GraphQL query that we sent.
What is GraphQL
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more.
Get many resources in a single request: GraphQL queries access not just the properties of one resource but also smoothly follow references between them. While typical REST APIs require loading from multiple URLs, GraphQL APIs get all the data your app needs in a single request.
GraphQL is about data trees, which allow us to get into relations. push back the data aggregation to the server side
- One Endpoint
- One Request
- Type System
GraphQL vs REST
REST
- Multiple endpoints for different data type
- Chained requests to get the data we need
- Over-fetch: We get more information from what we need.
- Under-fetch: we get less data so we need to make a lot of requests to get the info
GraphQL
- One Endpoint
- One Request with different mapping
- No Over-fetch
- No Under-fetch
- Type System
- Predictable
When to use it
REST
- Non-Interactive (System to System)
- Microservices
- Simple Object Hierarchy
- Repeated Simple queries
- Easier to develop
- more complex to consume by clients
GraphQL
- real time applications
- mobile applications
- complex object hierarchy
- complex query
- Complicated to develop
- Easier to consume by clients
Core Concepts
Schema: Describe the api in full, query, objects, datatypes and description. Some of its properties are
- Self-documenting
- Formed of Types
- Must have a Root Query Type
Types: It can be anything some of the types are
- Query
- Mutation
- Subscription
- Objects
- Enumeration
- Scalar
Resolvers: returns data for a given field
Data Source:
- Data Source
- Microservice
- Rest API
Mutation: will allow us to edit and add data
Subscription: a web socket base connection which will allow us to send real time messages once an action is executed.
Ingredients
VS Code (https://code.visualstudio.com/download)
.Net 5 (https://dotnet.microsoft.com/download)
Insomnia (https://insomnia.rest/download)
Dbeaver (https://dbeaver.io/download/)
HotChocolate
is an implementation of GraphQL and a framework for writing GraphQL servers in .Net Core.
We need to check the version of dotnet
dotnet --version
Now we need to install the entity framework tool
dotnet tool install --global dotnet-ef
Once thats finish let us create our application
dotnet new web -n TodoListGQL
Now we need to install the packages that we need
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package HotChocolate.AspNetCore
dotnet add package HotChocolate.Data.EntityFramework
dotnet add package GraphQL.Server.Ui.Voyager
Now lets check our application and check the source code, lets build the application and see if its running
dotnet build
dotnet run
Now let us start developing, the first thing we need to do is create our models and build our DbContext. Inside the root directory of our application let us create a new folder called Models and inside the Models folder let us create a 2 new classes called ItemData.cs and ItemList.cs
public class ItemData
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public bool Done { get; set; }
public int ListId { get; set; }
public virtual ItemList ItemList { get; set; }
}
public class ItemList
{
public ItemList()
{
ItemDatas = new HashSet<ItemData>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ItemData> ItemDatas { get; set; }
}
Now we need to create our application db context so inside the root directory we create a new folder called Data and inside the Data folder will create a new class called ApiDbContext
public class ApiDbContext : DbContext
{
public virtual DbSet<ItemData> Items {get;set;}
public virtual DbSet<ItemList> Lists {get;set;}
public ApiDbContext(DbContextOptions<ApiDbContext> options)
: base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ItemData>(entity =>
{
entity.HasOne(d => d.ItemList)
.WithMany(p => p.ItemDatas)
.HasForeignKey(d => d.ListId)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("FK_ItemData_ItemList");
});
}
}
Now we need to update our appsettings.json as well the startup class.
First let us open the appsettings and add the following code
"ConnectionStrings": {
"DefaultConnection" : "DataSource=app.db; Cache=Shared"
},
Now let us update the startup class to connect our application to the database
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApiDbContext>(options =>
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")
));
}
Now we want to build our database, to do that we need utilise the ef core migrations
dotnet ef migrations add "Initial Migrations"
dotnet ef database update
After the database update has completed successfully we can see we have a new folder called migrations which will contain the C# script which will be responsible on creating the database and its table Item.
we can verify that the database has been created since we can see the app.db file in our root directory as well we can see that use the SQLite browser (dbeaver) to verify that the table has been created successfully.
Now we need to start integrating GraphQL to our application, the first thing we are going to do is to add a new folder into the root of our applications which is called GraphQL.
Now inside the GraphQL folder we are going to create a new class called Query.cs
Our Query class will contain some methods which will return an IQueryable Result and this query class will be the endpoint we are going to utilise to get information back from the api
public class Query
{
// Will return all of our todo list items
// We are injecting the context of our dbConext to access the db
public IQueryable<ItemData> GetItem([Service] ApiDbContext context)
{
return context.Items;
}
}
Now we need to to update our startup class inside our ConfigureServices Method to utilise GraphQL and create an entry point for GraphQL and provide us with a schema construction
services.AddGraphQLServer()
.AddQueryType<Query>();
Now we need to update our endpoints
app.UseEndpoints(endpoints =>
{
endpoints.MapGraphQL();
});
app.UseGraphQLVoyager(new VoyagerOptions()
{
GraphQLEndPoint = "/graphql"
}, "/graphql-voyager");
Let us build our application and run it
dotnet build
dotnet run
// http://localhost:5000/graphql
When we navigate to http://localhost:5000/graphql we can see that we are utilising a UI which is provided for us by the HotChocolate nuget package that we installed. if we click on the schema button (book icon) we can see the main query that we have added called items based on the one we added in the query class.
Let us open our database with dbeaver and add some information manually there then lets go back to our url http://localhost:5000/graphql Now let us test our application
query{
items
{
id
title
}
}
so now let us update our query and introduce aliases which mean that we want to execute different commands in the same query similar to below
query {
a:items{
id
title
}
b:items{
id
title
}
c:items{
id
title
}
}
In this request we are only getting 1 of the requests back while the others are generating errors why is that happening.
The main reason behind this is that our application db context does not work in parallel which means when GraphQL try to execute the commands simultaneously it fails as the db context can only work single threaded.
To resolve this issue we need to use a new feature introduced in .Net 5 which is PooledDbContextFactory which we can use to resolve this error.
The first place we need to change is our startup class inside our ConfigureServices Method
services.AddPooledDbContextFactory<ApiDbContext>(options =>
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")
));
The AddPooledDbContextFactory is basically creating instance of the ApiDbContext and putting them in a pool, when ever the db context is needed we can take an instance from the pool then return it once we finish using it.
The next step we need to update the Query.cs
// So basically this attribute is pulling a db context from a pool
// using the db context
// returning the db context to the pool
[UseDbContext(typeof(ApiDbContext))]
public IQueryable<ItemData> GetItems([ScopedService] ApiDbContext context)
{
return context.Items;
}
Now let us run the application again and run the parallel queries again.
dotnet run
Now we can see everything is running as it should be, next we are going to try to pul the information from the list so we are going to get the parent list and all the items that belong to the list
let us add a new query in the query class
[UseDbContext(typeof(ApiDbContext))]
public IQueryable<ItemList> GetLists([ScopedService] ApiDbContext context)
{
return context.Lists;
}
Now let us try querying the data and check what do we get so inside insomnia we create a new request
query{
lists
{
name
itemDatas {
id
title
}
}
}
And we can see that the data which is being returned is not complete
So how do we solve this we need to enable projections inside our query to enable us to get obj children. Let us update the Query.cs class to the following
[UseDbContext(typeof(ApiDbContext))]
[UseProjection]
public IQueryable<ItemList> GetLists([ScopedService] ApiDbContext context)
{
return context.Lists;
}
Next we need to update the startup class inside the ConfigureServices method
// This will be the entry point and will provide us with a schema
// construction
services.AddGraphQLServer()
.AddQueryType<Query>()
.AddProjections();
Documentation
Now let us document our API, to accomplish this we need to update our models class first let us update the ItemData model
[GraphQLDescription("Used to define todo item for a specific list")]
public class ItemData
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
[GraphQLDescription("If the user has completed this item")]
public bool Done { get; set; }
[GraphQLDescription("The list which this item belongs to")]
public int ListId { get; set; }
public virtual ItemList ItemList { get; set; }
}
Now let us update our ItemList
[GraphQLDescription("Used to group the do list item into groups")]
public class ItemList
{
public ItemList()
{
ItemDatas = new HashSet<ItemData>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ItemData> ItemDatas { get; set; }
}
Now where can we see these documentation, since we have already added the GraphQL voyager nuget and we configured it in our startup class we need to visit this url: http://localhost:5000/graphql-voyager
And we can see a graphical representation of our API and we can see the documentation that we added.
Now we need to do some breakup between our Models and the documentation and in order for us to achieve this we will use types
Inside the GraphQL folder we need to create a new folders called Items and Lists. Once we create these folders let us create our first Type ListType.cs inside the Lists folder.
public class ListType : ObjectType<ItemList>
{
// since we are inheriting from objtype we need to override the functionality
protected override void Configure(IObjectTypeDescriptor<ItemList> descriptor)
{
descriptor.Description("Used to group the do list item into groups");
descriptor.Field(x => x.ItemDatas).Ignore();
descriptor.Field(x => x.ItemDatas)
.ResolveWith<Resolvers>(p => p.GetItems(default!, default!))
.UseDbContext<ApiDbContext>()
.Description("This is the list of to do item available for this list");
}
private class Resolvers
{
public IQueryable<ItemData> GetItems(ItemList list, [ScopedService] ApiDbContext context)
{
return context.Items.Where(x => x.ListId == list.Id);
}
}
}
Now we need to create the ItemType inside GraphQL ⇒ Items folder
// since we are inheriting from objtype we need to override the functionality
protected override void Configure(IObjectTypeDescriptor<ItemData> descriptor)
{
descriptor.Description("Used to define todo item for a specific list");
descriptor.Field(x => x.ItemList)
.ResolveWith<Resolvers>(p => p.GetList(default!, default!))
.UseDbContext<ApiDbContext>()
.Description("This is the list that the item belongs to");
}
private class Resolvers
{
public ItemList GetList(ItemData item, [ScopedService] ApiDbContext context)
{
return context.Lists.FirstOrDefault(x => x.Id == item.ListId);
}
}
Once the types has been added we need to update our startup class to take advantage of the types so inside our Startup class in the ConfigureServices method we need to update to the following
services.AddGraphQLServer()
.AddQueryType<Query>()
.AddType<ItemType>()
.AddType<ListType>()
.AddProjections();
Now let us build and run our application and check the schema
dotnet build
dotnet run
Now we need to add filtering and sorting we need to update the Query class to the below
public class Query
{
// Will return all of our todo list items
// We are injecting the context of our dbConext to access the db
// this is called a resolver
// So basically this attribute is pulling a db context from a pool
// using the db context
// returning the db context to the pool
[UseDbContext(typeof(ApiDbContext))]
[UseProjection] //=> we have remove it since we have used explicit resolvers
[UseFiltering]
[UseSorting]
public IQueryable<ItemData> GetItems([ScopedService] ApiDbContext context)
{
return context.Items;
}
[UseDbContext(typeof(ApiDbContext))]
[UseProjection] //=> we have remove it since we have used explicit resolvers
[UseFiltering]
[UseSorting]
public IQueryable<ItemList> GetLists([ScopedService] ApiDbContext context)
{
return context.Lists;
}
}
Then we need to update the startup class ConfigureServices method
services.AddGraphQLServer()
.AddQueryType<Query>()
.AddType<ListType>()
.AddType<ItemType>()
.AddProjections()
.AddSorting()
.AddFiltering();
Now let us create a new query with filtering to see what how it works filtering
query {
lists(where: {id: {eq: 1} })
{
id
name
itemDatas {
title
}
}
}
sorting query
query{
lists(order: {name: DESC})
{
id
name
}
}
Now we want to cover mutation, and Mutation is when we want to add, edit and delete data.
To implement mutation we need to add a new class inside our GraphQL folder and this mutation class will contain 2 methods 1 for adding Lists and 1 for adding list item
Will start by adding the input and output model so inside the GraphQL ⇒ List we add 2 files AddListInput and AddListPayload
public record AddListPayload(ListType list);
public record AddListInput(string name);
And now we need to add our mutation class inside the GraphQL folder we add a new a class called Mutation
// this attribute will help us utilise the multi threaded api db context
[UseDbContext(typeof(ApiDbContext))]
public async Task<AddListPayload> AddListAsync(AddListInput input, [ScopedService] ApiDbContext context)
{
var list = new ItemList
{
Name = input.name
};
context.Lists.Add(list);
await context.SaveChangesAsync();
return new AddListPayload(list);
}
Now we need to update our startup class
services.AddGraphQLServer()
.AddQueryType<Query>()
.AddType<ListType>()
.AddType<ItemType>()
.AddMutationType<Mutation>()
.AddProjections()
.AddSorting()
.AddFiltering();
Now let us test it, we create a new request in insomnia and utilise mutation instead of query
mutation{
addList(input: {
name: "Food"
})
{
list
{
name
}
}
}
Now we are going to add a second mutation to add list item so we start by adding the models inside the GraphQL ⇒ Items will add AddItemInput and AddItemPayload
public record AddItemInput(string title, string description, bool done, int listId);
public record AddItemPayload(ItemData item);
and we need now to update the mutation class
[UseDbContext(typeof(ApiDbContext))]
public async Task<AddItemPayload> AddItemAsync(AddItemInput input, [ScopedService] ApiDbContext context)
{
var item = new ItemData
{
Description = input.description,
Done = input.done,
Title = input.title,
ListId = input.listId
};
context.Items.Add(item);
await context.SaveChangesAsync();
return new AddItemPayload(item);
}
Now let us test it
mutation{
addItem(input: {
title: "Bring laptop",
description: "Bring the laptop with charger",
done: true,
listId: 1
})
{
item
{
id
title
}
}
}
Now we are going to go through subscriptions
A subscription is a real time event notification, we utilise a websocket to achieve this.
Inside the GraphQL folder will create a new class called Subscription.cs
[Subscribe]
[Topic]
public ItemList OnListAdded([EventMessage] ItemList list) => list;
And we need to update the startup class to take advantage of websockets we update the Configure method with the following
app.UseWebSockets();
The second part we need to update in startup class is the ConfigureServices method
services.AddGraphQLServer()
.AddQueryType<Query>()
.AddType<ListType>()
.AddType<ItemType>()
.AddMutationType<Mutation>()
.AddSubscriptionType<Subscription>()
.AddProjections()
.AddSorting()
.AddFiltering()
.AddInMemorySubscriptions();
Now we need to update our mutations so once we add a new list item we are sending an update to the subscriptions
// this attribute will help us utilise the multi threaded api db context
[UseDbContext(typeof(ApiDbContext))]
public async Task<AddListPayload> AddListAsync(
AddListInput input,
[ScopedService] ApiDbContext context,
[Service] ITopicEventSender eventSender,
CancellationToken cancellationToken)
{
var list = new ItemList
{
Name = input.name
};
context.Lists.Add(list);
await context.SaveChangesAsync(cancellationToken);
// we emit our subscription
await eventSender.SendAsync(nameof(Subscription.OnListAdded), list, cancellationToken);
return new AddListPayload(list);
}
Top comments (5)
Hi, I tried running one of the last queries on your github repo:
However an error occurred: "The field
lists
does not exist on the typeQuery
.". This is because the correct name islist
(without the s). I recommend fixing the article. :-)Thank you for your work!
Hi,
What is this record type in "public record AddItemPayload(ItemData item);" ?
How is that possible to add a method in a blank *.cs file?
I noticed 2 typos in your post:
dotnet add package GraphQL.Server.Ui.Voyage >> dotnet add package GraphQL.Server.Ui.Voyager
public IQueryable GetItem([Service] ApiDbContext context) >>public IQueryable GetItems([Service] ApiDbContext context)
Thanks for this tutorial!
It's not working with .Net 7 as you know there is only Program.cs after .Net 6
Following link does not exists as of writing this message:
github.com/mohamadlawand087/v28-Ne...
If you have any updated repo, let us know.
Thanks
I am a humen :D
Thanks ever so much for posting this. I was struggling with the endpoint configuration until I saw this post.