Steps on how to implement a basic GraphQL query endpoint with filtering, paging, and sorting, using HotChocolate and EFCore
See original post here: https://www.softwareis.cool/hotchocolate-and-efcore/
Going from a DbContext
to an API could not be easier with Hot Chocolate. You'll have a simple GraphQL endpoint to query (including filtering, paging and sorting) faster than you can say "representational state transfer". Let's start learning by doing!
*2022, Sept 15th - Updated to Net6 & HC12.13
- Requirements
- Model tweaks
- Install HotChocolate
- Service configurations
- Adding root queries
- Manually test
- Next steps
You can get here our starting project, or create your own, and follow along the tutorial. All our starting project has is:
We're making a web API so you'll need a web API project... not exactly rocket science. We're using an empty ASP.NET Core Web Application
.
You should have an EF model already existing within the solution. If you are not quite sure what I'm talking about, check how to get started with EF Core.
In our starting project, we are using the small model that you get from the "how to get started" sample, with Blogs
and Posts
. If you're using your own model, adapt our steps to fit your entities! It really is not that hard!
Because we will configure our DbContext when we register it for dependency injection, our context should not contain any hard-coded configuration and should pass any options during construction to the base class.
In our case this means deleting, within our DbContext
, the OnConfiguring(...)
method, and create a new empty constructor that allows for DbContextOptions
like so:
public BloggingContext(DbContextOptions options) : base(options)
{}
Resulting changes after this step, can be seen here.
Install packages HotChocolate.Data.EntityFramework
and HotChocolate.AspNetCore
in your project. You can do this from the Visual Studio package management UI, dotnet CLI, or package manager console. However you prefer!
Resulting changes after this step, can be seen here.
We'll need to register two services with our dependency injection container: GraphQL and our database context. For that, we go to our Startup
class and within our ConfigureServices(..)
method we add the following snippet:
// Register our DbContextPool
services.AddPooledDbContextFactory<BloggingContext>(b => b
//Context options
.UseSqlite("Data Source=blogging.db")
);
// Build our schema and register it.
services
.AddGraphQLServer();
For our last service configuration, we need to map our GraphQL endpoints.
Within our Startup
class, and within our Configure(...)
method, we configure the GraphQL endpoints by adding it to our route builder's UseEndpoints(...)
call:
app.UseEndpoints(endpoints =>
{
// Other stuff might already exist here...
// We map our GraphQL endpoint
endpoints.MapGraphQL();
});
For those things to work, you'll need to add the correct using
statements, which intellisense should suggest: using Microsoft.EntityFrameworkCore;
If we've done everything correctly, we should be able to start the project, navigate to /graphql
and see Banana Cake Pop
. This will be our GraphQL IDE!
(Also... cool people call it BCP
. Now you know.)
Resulting changes after this step, can be seen here.
We now have the base for our project. We're serving up an empty schema through our endpoint, and we have a GraphQL IDE that we can use to explore and interact with our endpoint. Time to actually define our schema!
This is where the magic happens. Let's add a root query, so our users can enter the schema through it. In our case, we're adding both blog
and post
:
In a new folder called Schema
, define your query class as follows:
using EFGetStarted;
using HotChocolate;
using HotChocolate.Data;
using HotChocolate.Types;
using System.Linq;
public class Queries
{
// The order of these HC annotations matters!
[UseDbContext(typeof(BloggingContext))]
[UsePaging]
[UseFiltering]
[UseSorting]
public IQueryable<Blog> GetBlogs([ScopedService] BloggingContext dbContext)
=> dbContext.Blogs;
// The order of these HC annotations matters!
[UseDbContext(typeof(BloggingContext))]
[UsePaging]
[UseFiltering]
[UseSorting]
public IQueryable<Post> GetPosts([ScopedService] BloggingContext dbContext)
=> dbContext.Posts;
}
Your namespaces might differ from ours and you can always adapt it to your own entities.
Last step, is modifying the schema builder in our Startup
to add the queries, and the middleware for filtering and sorting.
In our Startup class, in the ConfigureServices method, modify the section that adds the GraphQLServer so it looks like the following:
// Build our schema and register it.
services
.AddGraphQLServer()
.AddQueryType<Queries>()
.AddFiltering()
.AddSorting();
Resulting changes after this step, can be seen here.
If everything works out, you should be able to see your queries in your IDE.
Run your project, navigate to BCP
, go to Schema
and Refresh
.
That is it. We now how a queryable GraphQL endpoint with sorting, paging and filtering that depends only on our data model. How easy was that? And how easy will it be to maintain?
What now? Well... a lot of things! You might want to look into how to add mutations, subscriptions, data loader, relay support...
Don't worry, it sounds a lot harder that it is. There is a workshop explaining the details, steps and anything else you might be interested in right here.