dotnet-graphql
Skill1 starsChanged 7 months agoMITView on GitHub
Guides implementation of GraphQL with HotChocolate in .NET 8 projects following Clean Architecture. Creates the GraphQL project with single or multi-schema design, queries returning IQueryable for EF Core optimization, type extensions for computed fields, field hiding, error logging, and DI registration. Use when adding GraphQL support, creating queries, type extensions, or configuring GraphQL schemas.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- emaginebr/AvaBotword for word, first seen Apr 8, 2026
Other versions1 project uses a version of this.
Word for word · 1
- emaginebr/AvaBot★ 2100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository39 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- readme-generatorSkill
- add-react-i18nSkill
- band-album-campaignSkill
- band-music-reflectionSkill
- band-streaming-launchSkill
- ckm:banner-designSkill
- ckm:brandSkill
- deploy-prodSkill
- ckm:designSkill
- ckm:design-systemSkill
- docker-compose-configSkill
- doc-managerSkill
- dotnet-architectureSkill
- dotnet-doc-controllerSkill
- dotnet-envSkill
- dotnet-fluent-validationSkill
- dotnet-multi-tenantSkill
- dotnet-test-apiSkill
- dotnet-testSkill
- frontend-designSkill
- gitversionSkill
- maui-architectureSkill
- mermaid-chartSkill
- nauth-guideSkill
- nnews-guideSkill
- react-alertSkill
- react-architectureSkill
- react-archSkill
- react-modalSkill
- readme-generatorSkill
- ckm:slidesSkill
- social-media-assetsSkill
- social-media-schedulingSkill
- social-post-authoringSkill
- ckm:ui-stylingSkill
- ui-ux-pro-maxSkill
- wsl-dockerSkill
- ztools-guideSkill
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 2 are distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 97
Create GraphQLErrorLogger.cs — centralized error handling for all GraphQL operations.
- Very specific: line 284
Create an ObjectType<T> to hide sensitive fields.
- Very specific: line 309
Create files in Types/ — add derived/computed fields to entities without modifying the entity model.
- Very specific: line 413
Create GraphQLServiceExtensions.cs — the configuration varies by schema strategy.
- Very specific: line 525
Add to the API's Startup.cs.
- Distinctive: line 568
All endpoints expose the Banana Cake Pop interactive playground for development.
- Very specific: line 572
Add the GraphQL project reference to {Project}.Application.csproj:
- Distinctive: line 586
Add a method to the query class:
- Very specific: line 600
Create Types/{Entity}TypeExtension.cs using Pattern A, B, or C
- Very specific: line 608
Create Types/{Entity}Type.cs with descriptor.Ignore()
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
.NET GraphQL Implementation Guide (HotChocolate)
You are an expert assistant that helps developers implement GraphQL using HotChocolate in .NET 8 projects following Clean Architecture. You guide the user through creating the GraphQL layer with IQueryable-based queries, type extensions, and proper DI registration. The schema design is flexible — single schema or multiple schemas depending on the project's needs.
Input
The user will describe what to create or modify: $ARGUMENTS
Before generating code:
- Read the solution structure — Identify the project name prefix (e.g.,
Lofn,MyApp) - Read the DbContext — Find all
DbSet<T>entities available for GraphQL exposure - Read existing GraphQL files (if any) — Match existing patterns exactly
- Read the Startup/DI setup — Find the centralized DI class (usually
Application/Startup.cs) - Identify the auth setup — Find how authentication works (middleware, bearer token, etc.)
- Identify entity relationships — Understand navigation properties for type extensions
- Determine schema strategy — Ask or infer whether the project needs single schema, dual schema (public + admin), or custom named schemas
Architecture Overview
Project Structure
{Project}.GraphQL/
├── GraphQLServiceExtensions.cs ← DI registration, configures schema(s)
├── GraphQLErrorLogger.cs ← Diagnostic event listener for logging
├── {Project}.GraphQL.csproj ← NuGet dependencies
├── Queries/
│ └── {Entity}Query.cs ← Query class(es) — single or split by access level
├── Types/
│ ├── {Entity}TypeExtension.cs ← Computed fields via ObjectTypeExtension
│ └── {Entity}Type.cs ← ObjectType with field hiding (if needed)
└── (optional) Public/ & Admin/ ← Only if using multi-schema designDependency Flow
{Project}.API (Startup) → {Project}.GraphQL → {Project}.Infra (DbContext, Entities)
→ {Project}.Domain (Interfaces)Schema Strategy
Choose based on project requirements:
| Strategy | When to use | Structure |
| Single schema | All queries require auth, or no auth distinction needed | One Query class, one AddGraphQLServer() |
| Dual schema (public + admin) | Some queries are anonymous, others require auth with different data visibility | PublicQuery + AdminQuery, two AddGraphQLServer() calls |
| Multiple named schemas | Complex projects with distinct API consumers | Multiple named servers with separate query types |
Step-by-Step Implementation
Step 1: Create the GraphQL Project
dotnet new classlib -n {Project}.GraphQL -f net8.0
dotnet sln add {Project}.GraphQLStep 2: Add NuGet Packages
<!-- {Project}.GraphQL.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HotChocolate.AspNetCore" Version="14.3.0" />
<PackageReference Include="HotChocolate.AspNetCore.Authorization" Version="14.3.0" />
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="14.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\{Project}.Domain\{Project}.Domain.csproj" />
<ProjectReference Include="..\{Project}.Infra\{Project}.Infra.csproj" />
</ItemGroup>
</Project>Step 3: Error Logger
Create GraphQLErrorLogger.cs — centralized error handling for all GraphQL operations.
using System;
using System.Collections.Generic;
using HotChocolate;
using HotChocolate.Execution;
using HotChocolate.Execution.Instrumentation;
using HotChocolate.Execution.Processing;
using HotChocolate.Resolvers;
using Microsoft.Extensions.Logging;
namespace {Project}.GraphQL;
public class GraphQLErrorLogger : ExecutionDiagnosticEventListener
{
private readonly ILogger<GraphQLErrorLogger> _logger;
public GraphQLErrorLogger(ILogger<GraphQLErrorLogger> logger)
{
_logger = logger;
}
public override void RequestError(IRequestContext context, Exception exception)
{
_logger.LogError(exception, "GraphQL request error");
}
public override void ValidationErrors(IRequestContext context, IReadOnlyList<IError> errors)
{
foreach (var error in errors)
_logger.LogWarning("GraphQL validation error: {Message}", error.Message);
}
public override void SyntaxError(IRequestContext context, IError error)
{
_logger.LogWarning("GraphQL syntax error: {Message}", error.Message);
}
public override void ResolverError(IMiddlewareContext context, IError error)
{
_logger.LogError(error.Exception, "GraphQL resolver error on {Path}: {Message}", context.Path, error.Message);
}
public override void ResolverError(IRequestContext context, ISelection selection, IError error)
{
_logger.LogError(error.Exception, "GraphQL resolver error on {Field}: {Message}", selection.Field.Name, error.Message);
}
}Step 4: Query Classes
Queries always return IQueryable<T> for EF Core optimization. The structure depends on the schema strategy.
Single Schema — One Query class
using System.Linq;
using HotChocolate;
using HotChocolate.Authorization;
using HotChocolate.Data;
using HotChocolate.Types;
using {Project}.Infra.Context;
using Microsoft.AspNetCore.Http;
namespace {Project}.GraphQL.Queries;
public class Query
{
/// <summary>
/// List query with pagination, filtering, and sorting.
/// </summary>
[UseOffsetPaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<{Entity}> Get{Entity}s({DbContext} context)
=> context.{Entity}s.Where(e => e.Status == 1);
/// <summary>
/// Single record lookup by unique field (no pagination needed).
/// </summary>
[UseProjection]
public IQueryable<{Entity}> Get{Entity}BySlug({DbContext} context, string slug)
=> context.{Entity}s.Where(e => e.Status == 1 && e.Slug == slug);
/// <summary>
/// Authenticated query — use [Authorize] at method level for mixed schemas.
/// </summary>
[Authorize]
[UseOffsetPaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<{Entity}> GetMy{Entity}s(
{DbContext} context,
IHttpContextAccessor httpContextAccessor,
[Service] {IUserService} userService)
{
var user = userService.GetCurrentUser(httpContextAccessor.HttpContext!);
return context.{Entity}s.Where(e => e.OwnerId == user!.UserId);
}
}Dual Schema — Public + Admin (split query classes)
Public (anonymous):
using System.Linq;
using HotChocolate;
using HotChocolate.Data;
using HotChocolate.Types;
using {Project}.Infra.Context;
namespace {Project}.GraphQL.Public;
public class PublicQuery
{
[UseOffsetPaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<{Entity}> Get{Entity}s({DbContext} context)
=> context.{Entity}s.Where(e => e.Status == 1);
[UseProjection]
public IQueryable<{Entity}> Get{Entity}BySlug({DbContext} context, string slug)
=> context.{Entity}s.Where(e => e.Status == 1 && e.Slug == slug);
}Admin (authenticated, user-scoped):
using System.Linq;
using HotChocolate;
using HotChocolate.Authorization;
using HotChocolate.Data;
using HotChocolate.Types;
using {Project}.Infra.Context;
using Microsoft.AspNetCore.Http;
namespace {Project}.GraphQL.Admin;
[Authorize]
public class AdminQuery
{
private IQueryable<long> GetUserEntityIds(
{DbContext} context,
IHttpContextAccessor httpContextAccessor,
{IUserService} userService)
{
var user = userService.GetCurrentUser(httpContextAccessor.HttpContext!);
var userId = user!.UserId;
return context.{UserEntityMapping}s
.Where(m => m.UserId == userId)
.Select(m => m.{EntityId});
}
[UseOffsetPaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<{Entity}> GetMy{Entity}s(
{DbContext} context,
IHttpContextAccessor httpContextAccessor,
[Service] {IUserService} userService)
{
var entityIds = GetUserEntityIds(context, httpContextAccessor, userService);
return context.{Entity}s.Where(e => entityIds.Contains(e.{EntityId}));
}
}Critical patterns:
- Always return
IQueryable<T>— neverList<T>orTask<List<T>>. HotChocolate builds optimized SQL via EF Core - DbContext is injected as parameter — HotChocolate resolves it automatically from DI
- Attribute order matters:
[UseOffsetPaging]→[UseProjection]→[UseFiltering]→[UseSorting] [Authorize]— at class level (all queries require auth) or method level (mixed access)[Service]attribute — for custom interfaces not auto-resolved by HotChocolate- User scoping —
GetUserEntityIds()returnsIQueryable<long>for SQL subquery composition (no materialization)
Step 5: Field Hiding (ObjectType)
Create an ObjectType<T> to hide sensitive fields. Used when certain fields should not be exposed in a schema.
using HotChocolate.Types;
using {Project}.Infra.Context;
namespace {Project}.GraphQL.Types;
public class {Entity}Type : ObjectType<{Entity}>
{
protected override void Configure(IObjectTypeDescriptor<{Entity}> descriptor)
{
descriptor.Ignore(e => e.OwnerId);
descriptor.Ignore(e => e.{SensitiveNavigationProperty});
}
}Guidelines:
- Hide: owner IDs, internal user associations, internal status fields, audit fields
- In dual-schema: register ONLY on the schema that needs hiding (e.g., public but not admin)
- In single schema: register globally — the fields are hidden for everyone
Step 6: Type Extensions (Computed Fields)
Create files in Types/ — add derived/computed fields to entities without modifying the entity model.
Pattern A: ObjectTypeExtension with inline resolver (for field transformations, async lookups)
using HotChocolate.Types;
using {Project}.Infra.Context;
namespace {Project}.GraphQL.Types;
public class {Entity}TypeExtension : ObjectTypeExtension<{Entity}>
{
protected override void Configure(IObjectTypeDescriptor<{Entity}> descriptor)
{
// Ensure the source field is included in SQL projection
descriptor.Field(t => t.{SourceField}).IsProjected(true);
descriptor
.Field("{computedFieldName}")
.Type<StringType>()
.Resolve(async ctx =>
{
var entity = ctx.Parent<{Entity}>();
if (string.IsNullOrEmpty(entity.{SourceField})) return null;
var service = ctx.Service<{IService}>();
return await service.{Method}(entity.{SourceField});
});
}
}Pattern B: ObjectTypeExtension with database lookup (for related entity data)
using System.Linq;
using HotChocolate.Types;
using {Project}.Infra.Context;
using Microsoft.EntityFrameworkCore;
namespace {Project}.GraphQL.Types;
public class {Entity}TypeExtension : ObjectTypeExtension<{Entity}>
{
protected override void Configure(IObjectTypeDescriptor<{Entity}> descriptor)
{
descriptor
.Field("{computedFieldName}")
.Type<StringType>()
.Resolve(async ctx =>
{
var entity = ctx.Parent<{Entity}>();
var dbContext = ctx.Service<{DbContext}>();
var result = await dbContext.{RelatedEntities}
.Where(r => r.{EntityId} == entity.{EntityId})
.OrderBy(r => r.{SortField})
.Select(r => r.{TargetField})
.FirstOrDefaultAsync();
if (string.IsNullOrEmpty(result)) return null;
var service = ctx.Service<{IService}>();
return await service.{Method}(result);
});
}
}Pattern C: ExtendObjectType attribute with method resolver (for simple computed fields)
using System.Linq;
using HotChocolate;
using HotChocolate.Types;
using {Project}.Infra.Context;
namespace {Project}.GraphQL.Types;
[ExtendObjectType(typeof({Entity}))]
public class {Entity}TypeExtension
{
public int Get{ComputedField}(
[Parent] {Entity} entity,
[Service] {DbContext} context)
{
return context.{RelatedEntities}.Count(r => r.{EntityId} == entity.{EntityId} && r.Status == 1);
}
}When to use each pattern:
- Pattern A — Transform a field value (e.g., filename → URL). Use
IsProjected(true)to ensure the source field is in SQL - Pattern B — Computed field requires querying a related table not available via navigation property
- Pattern C — Simple synchronous or single-expression computed fields. Uses
[Parent]and[Service]attributes
Key elements:
.IsProjected(true)— ensures the source field is included in SQL even if not in the GraphQL queryctx.Parent<T>()— accesses the parent entity being extendedctx.Service<T>()— resolves a service from DI container- Null check — always guard before resolving
Step 7: DI Registration (GraphQLServiceExtensions)
Create GraphQLServiceExtensions.cs — the configuration varies by schema strategy.
Single Schema
using HotChocolate.Execution.Configuration;
using HotChocolate.Execution.Options;
using HotChocolate.Types.Pagination;
using {Project}.GraphQL.Queries;
using {Project}.GraphQL.Types;
using Microsoft.Extensions.DependencyInjection;
namespace {Project}.GraphQL;
public static class GraphQLServiceExtensions
{
public static IServiceCollection Add{Project}GraphQL(this IServiceCollection services)
{
services
.AddGraphQLServer()
.AddAuthorization()
.AddDiagnosticEventListener<GraphQLErrorLogger>()
.AddQueryType<Query>()
.AddType<{Entity}Type>() // Field hiding (if needed)
.AddTypeExtension<{Entity}TypeExtension>() // Computed fields
.SetPagingOptions(new PagingOptions
{
MaxPageSize = 50,
DefaultPageSize = 10,
IncludeTotalCount = true
})
.AddProjections()
.AddFiltering()
.AddSorting()
.ModifyCostOptions(o => o.MaxFieldCost = 8000);
return services;
}
}Dual Schema (Public + Admin)
using HotChocolate.Execution.Configuration;
using HotChocolate.Execution.Options;
using HotChocolate.Types.Pagination;
using {Project}.GraphQL.Admin;
using {Project}.GraphQL.Public;
using {Project}.GraphQL.Types;
using Microsoft.Extensions.DependencyInjection;
namespace {Project}.GraphQL;
public static class GraphQLServiceExtensions
{
public static IServiceCollection Add{Project}GraphQL(this IServiceCollection services)
{
// ── Public Server (anonymous access) ──
services
.AddGraphQLServer()
.AddAuthorization()
.AddDiagnosticEventListener<GraphQLErrorLogger>()
.AddQueryType<PublicQuery>()
.AddType<{Entity}Type>() // Field hiding (only on public)
.AddTypeExtension<{Entity}TypeExtension>() // Computed fields
.SetPagingOptions(new PagingOptions
{
MaxPageSize = 50,
DefaultPageSize = 10,
IncludeTotalCount = true
})
.AddProjections()
.AddFiltering()
.AddSorting()
.ModifyCostOptions(o => o.MaxFieldCost = 8000);
// ── Admin Server (authenticated access) ──
services
.AddGraphQLServer("admin")
.AddAuthorization()
.AddDiagnosticEventListener<GraphQLErrorLogger>()
.AddQueryType<AdminQuery>()
// NO {Entity}Type here — admin sees all fields
.AddTypeExtension<{Entity}TypeExtension>() // Same computed fields
.SetPagingOptions(new PagingOptions
{
MaxPageSize = 50,
DefaultPageSize = 10,
IncludeTotalCount = true
})
.AddProjections()
.AddFiltering()
.AddSorting()
.ModifyCostOptions(o => o.MaxFieldCost = 8000);
return services;
}
}Key configuration:
AddAuthorization()— enables[Authorize]attribute supportAddDiagnosticEventListener<GraphQLErrorLogger>()— connects error loggingAddType<{Entity}Type>()— field hiding (register only where needed)- Type extensions — registered on all schemas that need computed fields
- Paging options —
MaxPageSize=50,DefaultPageSize=10,IncludeTotalCount=true AddProjections()+AddFiltering()+AddSorting()— HotChocolate EF Core integrationModifyCostOptions—MaxFieldCost = 8000prevents expensive nested queries
Step 8: API Startup — Endpoint Mapping
Add to the API's Startup.cs. Configuration varies by schema strategy.
Single Schema
// ConfigureServices:
services.Add{Project}GraphQL();
// Configure (middleware pipeline):
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapGraphQL("/graphql"); // Single endpoint
});Dual Schema
// ConfigureServices:
services.Add{Project}GraphQL();
// Configure (middleware pipeline):
// Authentication runs conditionally — skip for public endpoint
app.UseWhen(
context => !context.Request.Path.StartsWithSegments("/graphql")
|| context.Request.Path.StartsWithSegments("/graphql/admin"),
branch => branch.UseAuthentication()
);
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapGraphQL("/graphql").AllowAnonymous(); // Public schema
endpoints.MapGraphQL("/graphql/admin", "admin"); // Admin schema
});All endpoints expose the Banana Cake Pop interactive playground for development.
Step 9: Add Project Reference
Add the GraphQL project reference to {Project}.Application.csproj:
<ProjectReference Include="..\{Project}.GraphQL\{Project}.GraphQL.csproj" />And call Add{Project}GraphQL() from the centralized Startup.cs in Application.
Adding New Elements
To add a query:
Add a method to the query class:
[UseOffsetPaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<{Entity}> Get{Entity}s({DbContext} context)
=> context.{Entity}s.Where(e => e.Status == 1);For authenticated queries, add [Authorize] (method level for single schema, class level for dedicated admin query class).
To add a type extension:
- Create
Types/{Entity}TypeExtension.csusing Pattern A, B, or C - Register in
GraphQLServiceExtensions.cs:
.AddTypeExtension<{Entity}TypeExtension>()To add field hiding:
- Create
Types/{Entity}Type.cswithdescriptor.Ignore() - Register in
GraphQLServiceExtensions.cs:
.AddType<{Entity}Type>()To add a new named schema:
services
.AddGraphQLServer("{schemaName}")
.AddQueryType<{SchemaName}Query>()
// ... same configuration chain
// Endpoint:
endpoints.MapGraphQL("/graphql/{schemaName}", "{schemaName}");Checklist
| # | File | Action | Description |
| 1 | {Project}.GraphQL.csproj | Create | Project with HotChocolate packages |
| 2 | GraphQLErrorLogger.cs | Create | Diagnostic event listener for error logging |
| 3 | Query class(es) | Create | Query.cs (single) or PublicQuery.cs + AdminQuery.cs (dual) |
| 4 | Types/{Entity}Type.cs | Create | ObjectType with field hiding (if needed) |
| 5 | Types/{Entity}TypeExtension.cs | Create | Computed fields via type extensions (one per entity) |
| 6 | GraphQLServiceExtensions.cs | Create | DI registration for schema(s) |
| 7 | Application/Startup.cs | Modify | Call Add{Project}GraphQL() |
| 8 | API/Startup.cs | Modify | Add endpoint mapping and auth middleware |
| 9 | Application .csproj | Modify | Add project reference to GraphQL project |
Response Guidelines
- Read existing files first — If the GraphQL project already exists, match its patterns exactly
- Read the DbContext — Identify all
DbSet<T>entities and their relationships - Determine schema strategy — Single or multi-schema based on the project's auth and access needs
- Always return
IQueryable<T>— Never materialize queries in GraphQL resolvers - Use attribute order —
[UseOffsetPaging]→[UseProjection]→[UseFiltering]→[UseSorting] - Register type extensions on all relevant schemas — Computed fields should be available where needed
- Use
IsProjected(true)— When a computed field depends on a source field - Match the project's auth pattern — Check how bearer tokens and user sessions are resolved
- Check for null values — Always guard against null in resolvers before calling services
- Don't force dual-schema — Use single schema when there's no need for separate public/admin access
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

