codewithmukesh/dotnet-claude-kit1 file

Minimal API

>

Specification
Skill ID
codewithmukesh/dotnet-claude-kit/minimal-api
Publisher
codewithmukesh
Repository
dotnet-claude-kit
Installs
0
Files
1
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

codewithmukesh/dotnet-claude-kit/minimal-apiInstalls these files
  • SKILL.md

What this skill tells the agent

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.

// Extensions/IEndpointGroup.cs
public interface IEndpointGroup
{
    void Map(IEndpointRouteBuilder app);
}
// Extensions/EndpointExtensions.cs
public 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;
    }
}
// Program.cs — this NEVER changes when adding endpoints
var app = builder.Build();
app.MapEndpoints();
app.Run();
// Features/Orders/OrderEndpoints.cs — one file per endpoint group
public 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.

// GOOD — TypedResults with union return type
private 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.

// Route parameters
app.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 binding
app.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.

// Apply the canonical filter (see error-handling skill) to a mutating endpoint
group.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.

// Program.cs — service registration only, no endpoint wiring
builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}
app.MapEndpoints(); // auto-discovers all IEndpointGroup implementations

// Endpoint metadata enriches the OpenAPI spec
group.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

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("api", opt =>
    {
        opt.PermitLimit = 100;
        opt.Window = TimeSpan.FromMinutes(1);
    });
});

// Apply inside an IEndpointGroup.Map method
var group = app.MapGroup("/api/orders")
    .WithTags("Orders")
    .RequireRateLimiting("api");

Output Caching

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

// BAD — endpoints scattered in Program.cs
app.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...

Use this skill in a project

Open any RiverX project, search the Skills panel for codewithmukesh/dotnet-claude-kit/minimal-api, and install it in one click.

Use this skill