Guides.NET

ASP.NET Core GraphQL with Hot Chocolate and EF Core

Create a .NET GraphQL API with Hot Chocolate, EF Core, PostgreSQL, Serilog, health checks, xUnit, and Testcontainers using Better Fullstack.

Updated October 6, 2026aspnet-coregraphqlhot-chocolateef-core

Use this stack when a .NET backend should expose GraphQL instead of REST, with EF Core and PostgreSQL ready to back the resolvers.

npm create better-fullstack@latest my-graphql-api -- \  --ecosystem dotnet \  --dotnet-web-framework aspnet-minimal \  --dotnet-orm ef-core \  --dotnet-auth none \  --dotnet-api graphql-hotchocolate \  --dotnet-testing xunit testcontainers-dotnet \  --dotnet-job-queue none \  --dotnet-realtime none \  --dotnet-observability serilog health-checks \  --dotnet-validation none \  --dotnet-caching none \  --dotnet-deploy docker \  --dotnet-libraries none \  --database postgres

This matches the .NET GraphQL template preset, with every option written out.

What this creates

  • An ASP.NET Core application with HotChocolate.AspNetCore, registered through AddGraphQLServer() and mapped with MapGraphQL().
  • A Query type with one hello field.
  • An EF Core AppDbContext with a Todos set, using the Npgsql provider.
  • Serilog console logging and ASP.NET Core health checks at /health.
  • An xUnit test project that references Testcontainers, plus a Dockerfile.

Generated shape

txt
my-graphql-api/
├── my_graphql_api.csproj
├── my_graphql_api.http
├── Program.cs
├── appsettings.json
├── appsettings.Development.json
├── Dockerfile
└── my_graphql_api.Tests/
    ├── my_graphql_api.Tests.csproj
    └── ApiTests.cs

Program.cs holds service registration, the TodoItem model, AppDbContext, and the Query class.

Prerequisites

  • .NET SDK 10 or newer.
  • PostgreSQL for the data context. The hello field works without a database.
  • Docker if you write tests that start containers through Testcontainers.

Run and verify

bash
cd my-graphql-api
dotnet restore
dotnet run

Send a query to the URL ASP.NET Core prints at startup:

bash
curl -X POST http://localhost:5000/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{ hello }"}'

Hot Chocolate exposes the Hello() method as the hello field. Run the generated test project by path, since no solution file is generated:

bash
dotnet test my_graphql_api.Tests/my_graphql_api.Tests.csproj

Connect the schema to EF Core

The generated schema does not read from the database yet. AppDbContext is registered, but Query only returns a string:

csharp
builder.Services.AddGraphQLServer().AddQueryType<Query>();

public sealed class Query
{
    public string Hello() => "Hello from my-graphql-api";
}

Hot Chocolate injects services into resolver parameters marked with [Service], so a first data field can take the context directly:

csharp
public sealed class Query
{
    public string Hello() => "Hello from my-graphql-api";

    public IQueryable<TodoItem> GetTodos([Service] AppDbContext db) => db.Todos.OrderBy(todo => todo.Id);
}

Nothing creates the Todos table at startup. Microsoft.EntityFrameworkCore.Design is already referenced, so generate and apply a migration before querying it:

bash
dotnet tool install --global dotnet-ef
dotnet ef migrations add InitialCreate
dotnet ef database update

Tests

The test project boots the application with WebApplicationFactory<Program> and asserts that / returns 200. It references the core Testcontainers package but contains no container test. For a PostgreSQL container, add the Testcontainers.PostgreSql module to the test project and point ConnectionStrings:DefaultConnection at the container in a custom factory.

When to choose it

Choose Hot Chocolate when clients need to pick fields and combine data in one request. Choose --dotnet-api minimal-api when a handful of REST endpoints is enough; the EF Core guide covers that path.

Compatibility notes

  • --dotnet-api graphql-hotchocolate replaces the Minimal API todo endpoints, so /api/todos is not mapped.
  • --dotnet-orm ef-core chooses Npgsql for --database postgres and the SQLite provider for sqlite.
  • --dotnet-observability accepts several values. This command combines Serilog with health checks; AddHealthChecks() has no database check.

Deployment notes

The Dockerfile publishes with the .NET 10 SDK and runs on the ASP.NET 10 runtime image, listening on port 8080 through ASPNETCORE_URLS. Set ConnectionStrings__DefaultConnection and apply migrations before routing traffic.

Troubleshooting

SymptomCheck
The .http file's /api/todos request returns 404The generated .http file still lists /api/todos; GraphQL requests go to /graphql.
relation "Todos" does not existCreate and apply an EF Core migration.
A new resolver field is missingPublic methods named GetX become field x; rebuild and restart the app.
No containers start during testsThe generated test does not use Testcontainers yet.
localhost:5000 returns 403 on macOSAirPlay Receiver also listens on port 5000. Start the app with another port, for example PORT=5080 dotnet run.
The generated README mentions TypeScript and npmThe .NET scaffold currently writes the TypeScript README. Use the commands in this guide.

Next steps

GitHub Sponsors