You are implementing or reviewing a HotChocolate GraphQL server in .NET. All guidance targets HotChocolate 16.6 or newer — do not apply it to v15-or-older codebases without checking the migration guides. This skill defines how ChilliCream expects that code to look. It covers implementation — resolvers, data access, DataLoaders, pagination. Schema design (SDL shape, naming, nullability) belongs to the sibling skill graphql-schema-design; use both when a task spans design and implementation.
Core principles
-
Implementation-first. Write plain C# — static resolver classes with attributes like
[QueryType],[MutationType],[ObjectType<T>],[DataLoader]— and let HotChocolate infer the schema. Do not use schema-first SDL binding, and reach for descriptor/Configureoverrides only for what attributes cannot express. -
All data access flows through DataLoaders. A resolver for a relation (any non-root field that fetches data) never queries a
DbContextor repository directly — it calls a DataLoader. Direct queries create N+1 loads and bypass request-scoped caching. -
Top-level
byIdfields use DataLoaders too. Root lookups are batchable: clients batch them with field aliases in a single operation and with request/variable batching across operations. A root resolver that hits the database directly forfeits that coalescing. -
DataLoaders are source-generated. Write a static method annotated with
[DataLoader]; never hand-write a class deriving fromBatchDataLoader/GroupedDataLoader. The generator emits the class, interface, and registration. -
Projections flow through
QueryContext<T>. Every DataLoader accepts aQueryContext<TValue>parameter so consumers can pass selections down to the database, and always pins its key into the projection (query.Include(x => x.Id)) so the key survives narrowing. The flip side: every resolver that reads parent properties declares them with[Parent(requires: nameof(...))]so projections keep those fields available. -
Pagination is cursor-based and ordered. Paginated fields use
PagingArguments+ToPageAsync/ToBatchPageAsync; every paginated query has a deterministic order ending in the key, and paging on non-root types goes through a batch-paging DataLoader. Never offset pagination — relative cursors cover jump-to-page needs.
Project setup
Every HotChocolate project references the source generator as an analyzer-only package and names its module explicitly:
<PackageReference Include="HotChocolate.AspNetCore" />
<PackageReference Include="HotChocolate.Data" />
<PackageReference Include="HotChocolate.Types.Analyzers">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Properties/ModuleInfo.cs holds one line, naming the module after the domain — never rely on the assembly-name-derived default:
[assembly: Module("RecommendationTypes")]
This specifies the name of the generated method that registers all GraphQL-related types of the project — types and DataLoaders alike. General server settings live in a shared AddDefaultSettings extension so the main chain shows only schema-specific choices — see references/server-setup.md:
builder
.AddGraphQL()
.AddDefaultSettings()
.AddRecommendationTypes();
Reference file index
Detailed rules with worked wrong-vs-right examples. Load only what the current task touches.
| Reference | When to load |
|---|---|
| references/dataloaders.md | Any DataLoader, any resolver that fetches data, any N+1 concern, any byId field |
| references/pagination.md | Any paginated field, [UseConnection], PagingArguments, connection/page types, sorting on collections |
| references/resolvers.md | Extending entities with [ObjectType<T>], [BindMember], [Parent(requires:)], root type classes, field naming |
| references/subgraph.md | The project is a subgraph — detect via .AddSourceSchemaDefaults() in the builder chain or a schema-settings.json file. Covers [Lookup] on byId fields, entity references, cost enforcement, node-as-lookup |
| references/server-setup.md | New projects, Program.cs / builder chains, csproj packages, [assembly: Module], AddDefaultSettings |
| references/mutations.md | Any mutation, [MutationType], mutation conventions, [Error], payload/input shapes |
| references/filtering.md | Any UseFiltering usage, filter input types, deciding whether a field should be filterable |
| references/sorting.md | Any UseSorting usage, sort input types, deciding whether a field should be client-sortable |
More references will be added as the skill grows.