REST endpoints with ASP.NET Core Minimal APIs

Minimal APIs in ASP.NET Core map HTTP routes to handlers without a controller class. They are still ASP.NET Core (the product is .NET, not “.NET Core” except as history). Use them for small HTTP surfaces and vertical slices. Controllers remain fine for large apps with filters and conventions you already have.

A slice, not a dump of Program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IOrders, SqlOrders>();
var app = builder.Build();
app.MapGet("/orders/{id:guid}", async (Guid id, IOrders orders, CancellationToken ct) =>
{
    var order = await orders.Get(id, ct);
    return order is null ? Results.NotFound() : Results.Ok(order);
});
app.Run();
  • Bind route/query/body to parameters; inject services the same way as MVC.
  • Return IResult (Results.Ok, TypedResults) so status codes stay explicit.
  • Group with MapGroup("/orders") and attach auth/filters once.

When to prefer controllers

Dozens of similar resources, shared filters, API versioning attributes, or a team that already thinks in controller actions. Minimal APIs do not magically make a 200-endpoint monolith simpler; folder-by-feature does.

HTTP hygiene

  • GET/HEAD have no side effects; do not “GET to delete.”
  • Use 401/403/404/409/422 honestly. 200 + { success: false } is a lie.
  • Problem details (application/problem+json) beat ad-hoc error JSON.

What breaks it

GET cancels an order, and a 200 body says success: false. Cancel with POST. Return 404 when the order is missing and 409 when it is already cancelled.

app.MapGet("/orders/{id:guid}/cancel", async (Guid id, IOrders orders, CancellationToken ct) =>
{
    await orders.Cancel(id, ct);
    return Results.Ok(new { success = false });
});
app.MapPost("/orders/{id:guid}/cancel", async (Guid id, IOrders orders, CancellationToken ct) =>
{
    var result = await orders.Cancel(id, ct);
    return result switch
    {
        CancelResult.Missing => Results.NotFound(),
        CancelResult.Conflict => Results.Conflict(),
        _ => Results.NoContent()
    };
});

Pitfalls

  • One 3,000-line Program.cs.
  • Capturing scoped DbContext in a singleton lambda.
  • Calling it REST because the path has a noun. See architectural styles.

Auth: REST Minimal API with Authentication.