Minimal Api

codewithmukesh/dotnet-claude-kit/skills/minimal-api

by codewithmukesh23300897f4d1No license754 starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 2 months ago

.NET 10 minimal APIs — the default for building HTTP endpoints. Covers MapGroup, endpoint filters, TypedResults, OpenAPI metadata, parameter binding, and route conventions. Load this skill when creating API endpoints, configuring routing, setting up OpenAPI documentation, or when the user mentions "endpoint", "MapGet", "MapPost", "MapGroup", "TypedResults", "route", "minimal API", "OpenAPI", "swagger", "rate limiting", or "output caching".

Instructions only

Minimal APIs (.NET 10)

Core Principles

  1. Minimal APIs are the default — Use controllers only when migrating legacy code. Minimal APIs are lighter, faster, and compose well with any architecture style.
  2. Group endpoints with MapGroup — Never scatter individual MapGet/MapPost calls in Program.cs. Group related endpoints together.
  3. Use TypedResults for OpenAPI — TypedResults.Ok(value) gives you compile-time type safety AND correct OpenAPI documentation. Results.Ok(value) does not.
  4. Metadata over comments — Use .WithName(), .WithTags(), .WithSummary() to document endpoints. The metadata feeds into OpenAPI specs.

Patterns

Endpoint Group Auto-Discovery (Required Pattern)

Every endpoint group lives in its own file and implements IEndpointGroup. A single app.MapEndpoints() call in Program.cs discovers and registers all groups automatically. Program.cs never changes when you add new endpoint groups.

csharp
// Extensions/IEndpointGroup.cspublic interface IEndpointGroup{    void Map(IEndpointRouteBuilder app);}
csharp
// Extensions/EndpointExtensions.cspublic static class EndpointExtensions{    public static WebApplication MapEndpoints(this WebApplication app)    {        var groups = typeof(Program).Assembly            .GetTypes()            .Where(t => t.IsAssignableTo(typeof(IEndpointGroup)) && !t.IsInterface && !t.IsAbstract)            .Select(Activator.CreateInstance)            .Cast<IEndpointGroup>();
        foreach (var group in groups)            group.Map(app);
        return app;    }}
csharp
// Program.cs — this NEVER changes when adding endpointsvar app = builder.Build();app.MapEndpoints();app.Run();
csharp
// Features/Orders/OrderEndpoints.cs — one file per endpoint grouppublic sealed class OrderEndpoints : IEndpointGroup{    public void Map(IEndpointRouteBuilder app)    {        var group = app.MapGroup("/api/orders").WithTags("Orders");
        group.MapPost("/", CreateOrder)            .WithName("CreateOrder")            .WithSummary("Create a new order")            .Produces<OrderResponse>(StatusCodes.Status201Created)            .ProducesValidationProblem()            .RequireAuthorization();
        group.MapGet("/{id:guid}", GetOrder)            .WithName("GetOrder")            .Produces<OrderResponse>()            .ProducesProblem(StatusCodes.Status404NotFound);
        group.MapGet("/", ListOrders)            .WithName("ListOrders")            .Produces<PagedList<OrderResponse>>();    }
    private static async Task<Results<Created<OrderResponse>, ValidationProblem>> CreateOrder(        CreateOrderRequest request,        ISender sender,        CancellationToken ct)    {        var result = await sender.Send(new CreateOrder.Command(request.CustomerId, request.Items), ct);        return result.IsSuccess            ? TypedResults.Created($"/api/orders/{result.Value.Id}", result.Value)            : TypedResults.ValidationProblem(result.Errors);    }
    private static async Task<Results<Ok<OrderResponse>, NotFound>> GetOrder(        Guid id,        ISender sender,        CancellationToken ct)    {        var result = await sender.Send(new GetOrder.Query(id), ct);        return result.IsSuccess            ? TypedResults.Ok(result.Value)            : TypedResults.NotFound();    }
    private static async Task<Ok<PagedList<OrderResponse>>> ListOrders(        [AsParameters] ListOrdersQuery query,        ISender sender,        CancellationToken ct)    {        var result = await sender.Send(query, ct);        return TypedResults.Ok(result);    }}

TypedResults for Type-Safe Responses

TypedResults provides compile-time guarantees and automatic OpenAPI schema generation.

csharp
// GOOD — TypedResults with union return typeprivate static async Task<Results<Ok<Product>, NotFound, ValidationProblem>> GetProduct(    Guid id,    AppDbContext db,    CancellationToken ct){    var product = await db.Products.FindAsync([id], ct);    return product is not null        ? TypedResults.Ok(product)        : TypedResults.NotFound();}

Parameter Binding

.NET 10 minimal APIs bind parameters from route, query, header, body, and DI automatically.

csharp
// Route parametersapp.MapGet("/orders/{id:guid}", (Guid id) => ...);
// Query parameters (nullable = optional)app.MapGet("/orders", (int page, int? pageSize, string? status) => ...);
// Complex query parameters with [AsParameters]public record ListOrdersQuery(int Page = 1, int PageSize = 20, string? Status = null);app.MapGet("/orders", ([AsParameters] ListOrdersQuery query) => ...);
// Header bindingapp.MapGet("/orders", ([FromHeader(Name = "X-Correlation-Id")] string? correlationId) => ...);
// DI services are auto-resolved (no attribute needed)app.MapPost("/orders", (CreateOrderRequest request, ISender sender) => ...);

Endpoint Filters

Filters are the minimal API equivalent of action filters. Use them for cross-cutting concerns like validation, logging, and idempotency checks.

The canonical ValidationFilter<TRequest> implementation (FluentValidation, resolves the validator from DI and skips gracefully when none is registered) lives in the error-handling skill — use that one, don't re-implement it per project.

csharp
// Apply the canonical filter (see error-handling skill) to a mutating endpointgroup.MapPost("/", CreateOrder)    .AddEndpointFilter<ValidationFilter<CreateOrderRequest>>();
// Apply a filter to a group (affects all endpoints in the group)group.AddEndpointFilter<LoggingFilter>();

OpenAPI / Swagger Configuration

.NET 10 has built-in OpenAPI support. Use it instead of Swashbuckle.

csharp
// Program.cs — service registration only, no endpoint wiringbuilder.Services.AddOpenApi();
var app = builder.Build();
if (app.Environment.IsDevelopment()){    app.MapOpenApi();}app.MapEndpoints(); // auto-discovers all IEndpointGroup implementations
// Endpoint metadata enriches the OpenAPI specgroup.MapPost("/", CreateOrder)    .WithName("CreateOrder")    .WithSummary("Create a new order")    .WithDescription("Creates a new order for the specified customer with the given line items.")    .Produces<OrderResponse>(StatusCodes.Status201Created)    .ProducesValidationProblem()    .ProducesProblem(StatusCodes.Status500InternalServerError);

Rate Limiting

csharp
builder.Services.AddRateLimiter(options =>{    options.AddFixedWindowLimiter("api", opt =>    {        opt.PermitLimit = 100;        opt.Window = TimeSpan.FromMinutes(1);    });});
// Apply inside an IEndpointGroup.Map methodvar group = app.MapGroup("/api/orders")    .WithTags("Orders")    .RequireRateLimiting("api");

Output Caching

csharp
builder.Services.AddOutputCache(options =>{    options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromMinutes(5)));    options.AddPolicy("ByIdCache", builder => builder        .Expire(TimeSpan.FromMinutes(10))        .SetVaryByRouteValue("id"));});
group.MapGet("/{id:guid}", GetOrder)    .CacheOutput("ByIdCache");

Anti-patterns

Don't Put Endpoints in Program.cs

csharp
// BAD — endpoints scattered in Program.csapp.MapGet("/orders", async (AppDbContext db) => await db.Orders.ToListAsync());app.MapGet("/orders/{id}", async (Guid id, AppDbContext db) => await db.Orders.FindAsync(id));app.MapPost("/orders", async (Order order, AppDbContext db) => { /* ... */ });app.MapGet("/products", async (AppDbContext db) => await db.Products.ToListAsync());
// ALSO BAD — manual MapGroup calls in Program.cs (grows with every feature)app.MapGroup("/api/orders").WithTags("Orders").MapOrderEndpoints();app.MapGroup("/api/products").WithTags("Products").MapProductEndpoints();app.MapGroup("/api/customers").WithTags("Customers").MapCustomerEndpoints();// Program.cs grows every time you add a feature...
// GOOD — auto-discovered, Program.cs never changesapp.MapEndpoints(); // discovers all IEndpointGroup implementations

Don't Use Untyped Results

csharp
// BAD — Results.Ok doesn't contribute to OpenAPI schemaprivate static async Task<IResult> GetOrder(Guid id, AppDbContext db){    var order = await db.Orders.FindAsync(id);    return order is not null ? Results.Ok(order) : Results.NotFound();}
// GOOD — TypedResults with explicit union typeprivate static async Task<Results<Ok<Order>, NotFound>> GetOrder(Guid id, AppDbContext db){    var order = await db.Orders.FindAsync(id);    return order is not null ? TypedResults.Ok(order) : TypedResults.NotFound();}

Don't Return Domain Entities Directly

csharp
// BAD — leaks internal structure, can't evolve independentlyapp.MapGet("/orders/{id}", async (Guid id, AppDbContext db) =>    await db.Orders.Include(o => o.Items).FirstOrDefaultAsync(o => o.Id == id));
// GOOD — map to a response DTOapp.MapGet("/orders/{id}", async (Guid id, AppDbContext db) =>{    var order = await db.Orders        .Where(o => o.Id == id)        .Select(o => new OrderResponse(o.Id, o.Total, o.CreatedAt))        .FirstOrDefaultAsync();    return order is not null ? TypedResults.Ok(order) : TypedResults.NotFound();});

Decision Guide

ScenarioRecommendation
New HTTP APIIEndpointGroup per feature + app.MapEndpoints() auto-discovery
Existing MVC projectKeep controllers, migrate incrementally
OpenAPI documentationUse TypedResults + .WithName() + .WithSummary()
Request validationEndpoint filter with FluentValidation
Authentication/authorization.RequireAuthorization("PolicyName") on group or endpoint
Rate limitingAddRateLimiter + .RequireRateLimiting()
Response cachingAddOutputCache + .CacheOutput()
Complex model binding[AsParameters] with a record type

Source and attribution

Source:codewithmukesh/dotnet-claude-kitinskills/minimal-apiat commit2330089

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal

More from codewithmukesh/dotnet-claude-kit

Wrap Up

codewithmukesh

Captures end-of-session work, pending tasks and learnings into a handoff file, and reloads it at session start.

Productivity & Workflow754updated 2 months ago

Workflow Mastery

codewithmukesh

Claude Code workflow mastery for .NET developers. Covers parallel execution with git worktrees, plan mode strategy, verification loops, auto-formatting hooks, permission setup for dotnet CLI, prompting techniques, subagent patterns, and context discipline — token budget management, MCP-first navigation, lazy loading, and subagent isolation — all adapted for the .NET ecosystem. Load this skill when setting up Claude Code for a .NET project, optimizing workflows, running parallel sessions, when context is running low or sessions feel sluggish, when exploring a large codebase efficiently, or when the user mentions "productivity", "workflow", "parallel", "worktree", "plan mode", "permissions", "hooks", "10x", "setup Claude Code", "speed up development", "context", "tokens", "budget", "running out of context", "too many files", or "large codebase". Inspired by tips from Boris Cherny (creator of Claude Code) and the Anthropic team.

Awaiting classification754updated 2 months ago

Vertical Slice

codewithmukesh

Guides .NET developers in structuring applications with Vertical Slice Architecture, covering feature folders, endpoint grouping and handler patterns.

Software Development754updated 2 months ago

Testing

codewithmukesh

Testing strategy for .NET 10 applications. Covers xUnit v3, WebApplicationFactory for integration tests, Testcontainers for real database testing, Verify for snapshot testing, and the AAA pattern. Load this skill when writing tests, setting up test infrastructure, reviewing test coverage, or when the user mentions "test", "xUnit", "WebApplicationFactory", "Testcontainers", "integration test", "unit test", "bUnit", "snapshot test", "Verify", "test coverage", "AAA pattern", "WireMock", or "FakeTimeProvider".

Awaiting classification754updated 2 months ago

Tdd

codewithmukesh

Guided test-driven development workflow for .NET 10 using xUnit v3, WebApplicationFactory, Testcontainers, and Verify snapshots. Follows the strict red-green-refactor cycle. Use when: "TDD", "test-driven", "let's TDD this", "red green refactor", "write the test first", or when building a feature with clear acceptance criteria.

Awaiting classification754updated 2 months ago

Spec

codewithmukesh

Turns a vague feature idea into an agreed, persisted specification file through structured questioning rounds.

Productivity & Workflow754updated 2 months ago