If you are new to GraphQL and looking to build a strongly typed API with ASP.NET Core, you have probably heard about Hot Chocolate. It is one of the most mature and developer-friendly GraphQL server implementations for .NET. But jumping straight into a tutorial can feel overwhelming when you are still wrapping your head around what a schema actually is, or why you need a separate runtime package instead of just using System.Text.Json.
This guide walks through the essentials step by step, assuming you have a basic ASP.NET Core Web API project up and running and you understand what an HTTP controller is. We will not skip the "why" behind each decision. By the end, you will have a working GraphQL endpoint that you can query with GraphiQL, and you will understand the pieces that make it tick.
What Hot Chocolate actually gives you
Before installing anything, it helps to know what problem Hot Chocolate solves. In a traditional REST API, you often end up with many endpoints: /api/users, /api/users/5, /api/posts, and so on. GraphQL flips that model. You expose a single endpoint, usually /graphql, and the client tells the server exactly which fields it wants. Hot Chocolate is the server-side engine that parses those queries, validates them against your schema, and resolves them to your .NET code.
The library also gives you strongly typed schemas using C# classes, automatic validation, and excellent tooling like GraphiQL built in. For a beginner, that means less boilerplate around routing and serialization, and more focus on your business logic.
Project setup and installation
Start by creating a new ASP.NET Core Web API project. If you are using the .NET CLI, run:
dotnet new webapi -n GraphQLDemo
cd GraphQLDemo
dotnet add package HotChocolate
dotnet add package HotChocolate.AspNetCore
The HotChocolate package contains the core runtime, while HotChocolate.AspNetCore wires it into the ASP.NET Core pipeline. You will also want the HotChocolate.AspNetCore.Tests package if you plan to write unit tests later, but that is optional for now.
Configuring the service and the endpoint
Open Program.cs. In modern minimal API style, you register the GraphQL services with builder.Services.AddGraphQL and then map the endpoint with app.MapGraphQL. Here is what that looks like:
// Register GraphQL services
builder.Services.AddGraphQL(options =>
{
// Configure default query and mutation types
options.EnableMetrics = true;
});
// Configure the ASP.NET Core pipeline
var app = builder.Build();
// Map the GraphQL endpoint at /graphql
app.MapGraphQL();
That is the entire server-side setup. Notice there is no controller, no action method, and no JSON serialization logic to write. Hot Chocolate handles the HTTP layer, query parsing, and execution internally. If you are coming from a controller-based background, this shift can feel strange at first, but it is one of the reasons GraphQL feels so concise.
Defining your first type and resolver
In GraphQL, everything revolves around types. A type describes a shape of data, like a User or a Product. In Hot Chocolate, you define these as C# classes decorated with attributes. Let us model a simple User:
using HotChocolate;
public class User
{
// The Id field is the primary identifier
public int Id { get; set; }
// [GraphQLName] allows you to customize the exposed field name
[GraphQLName("userId")]
public string Username { get; set; } = string.Empty;
// [GraphQLDescription] shows up in schema documentation
[GraphQLDescription("The user's email address")]
public string Email { get; set; } = string.Empty;
}
Attributes like [GraphQLName] and [GraphQLDescription] are optional but incredibly useful when you want your schema to be self-documenting. Without them, Hot Chocolate exposes the exact C# property names as GraphQL field names.
Creating the query root
GraphQL needs a root query type that tells the server what top-level fields are available for clients to ask for. In Hot Chocolate, you create a class that inherits from ObjectType:
using HotChocolate;
public class Query
{
// [ExtendObjectType(typeof(Query))] marks this as the root query
[ExtendObjectType(typeof(Query))]
public class UserType
{
// [UseProjection] enables field selection by the client
[UseProjection]
public User GetUser(
[Service] IUserRepository repository,
int id)
{
return repository.GetById(id);
}
}
}
Notice the [UseProjection] attribute. This is what allows a client to request only the fields it cares about, such as { getUser(id: 1) { userId email } }. Without it, the server would return the entire User object regardless of what the client asked for.
We also injected IUserRepository here. Hot Chocolate supports dependency injection natively, so you can resolve services directly in your resolvers. This keeps your data access logic separate from your GraphQL plumbing.
Registering the schema
So far we have defined types and a query root, but the server does not know about them yet. You need to tell Hot Chocolate to compile these into a schema. In Program.cs, add:
builder.Services.AddGraphQL(options =>
{
options.AddType<Query>();
options.AddType<User>();
});
The AddType method scans the assembly for classes decorated with GraphQL attributes and registers them. If you are working in a large solution with many projects, you may need to specify the assembly explicitly, but for a simple demo project, this automatic scanning is perfect.
Testing with GraphiQL
Hot Chocolate ships with GraphiQL, an in-browser IDE for exploring your schema. Once your application is running, navigate to https://localhost:7123/graphql (or whatever port your dev server uses). You will see a query editor on the left and a documentation pane on the right.
Try running this query:
{
getUser(id: 1) {
userId
email
}
}
If everything is wired correctly, you should see a JSON response matching the shape of your query. If you get an error, check three things first: that Program.cs has app.MapGraphQL(), that your types are registered with AddType, and that your resolver method signature matches the field name exactly.
Common beginner pitfalls
- Forgetting
MapGraphQL: Without this call, requests to/graphqlwill 404 because ASP.NET Core does not know what middleware to run. - Async vs sync resolvers: Hot Chocolate prefers async methods for I/O-bound work, such as database calls. If you return a
Task<User>, decorate the method withasyncand useawait. Mixing sync and async without care can lead to deadlocks in certain hosting scenarios. - Circular references: If your
Usertype contains aList<Post>andPostcontains a reference back toUser, you will get infinite recursion during serialization. Use[Ignore]on the back-reference or implement a custom resolver that limits depth.
Where to go next
You now have a minimal but functional GraphQL server. From here, the natural next steps are adding mutations for writes, implementing pagination with cursor-based connections, and securing the endpoint with JWT bearer tokens. Hot Chocolate integrates cleanly with ASP.NET Core authentication middleware, so you do not need a separate GraphQL-specific auth library.
Do not worry about mastering every advanced feature on day one. The goal was to see how little code is required to get a typed, queryable API running, and to understand the mental model shift from REST endpoints to a single schema-driven endpoint. Once that clicks, the rest of the ecosystem will feel much easier to navigate.