ASP.NET Core 9 Minimal APIs vs Controllers: A Decision Guide
Minimal APIs are not just for tiny services. Here is the production trade-off matrix and how to scale them without losing structure.
- Author
- Randhir Jassal
- Published
- Reading time
- 8 min read
What minimal APIs give up — and what they give you back
The headline feature is reduced ceremony:
var app = WebApplication.Create();
app.MapGet("/health", () => Results.Ok(new { ok = true }));
app.Run();
Compared to MVC, you skip controllers, attribute routing, and the ASP.NET pipeline plumbing. You also skip — by default — model binding inheritance, action filters, and view rendering.
The structure problem and how to fix it
The naive "put every endpoint in Program.cs" approach falls apart at 50 routes. The fix is endpoint groups by feature.
// Features/Orders/OrderEndpoints.cs
public static class OrderEndpoints {
public static IEndpointRouteBuilder MapOrderEndpoints(this IEndpointRouteBuilder app) {
var group = app.MapGroup("/api/orders")
.RequireAuthorization()
.WithTags("Orders")
.WithOpenApi();
group.MapGet("/", ListOrders);
group.MapGet("/{id:guid}", GetOrder);
group.MapPost("/", CreateOrder).Accepts<CreateOrderDto>("application/json");
group.MapPatch("/{id:guid}/status", UpdateStatus);
return app;
}
private static async Task<Ok<List<OrderDto>>> ListOrders(
IOrderService svc, CancellationToken ct)
=> TypedResults.Ok(await svc.ListAsync(ct));
}
// Program.cs
app.MapOrderEndpoints();
app.MapCustomerEndpoints();
app.MapInvoiceEndpoints();
This scales — every feature folder owns its routes, handlers, and DTOs.
Filter equivalents
Action filters become endpoint filters. They're chainable like middleware and work per endpoint.
group.MapPost("/", CreateOrder)
.AddEndpointFilter<RateLimitFilter>()
.AddEndpointFilter<ValidateModelFilter>();
public class ValidateModelFilter : IEndpointFilter {
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext ctx,
EndpointFilterDelegate next)
{
foreach (var arg in ctx.Arguments) {
if (arg is IValidatable v && !v.IsValid())
return TypedResults.BadRequest(v.Errors());
}
return await next(ctx);
}
}
When to choose controllers
- You need MVC views (Razor pages, HTML rendering)
- You need attribute-based authorization with complex policies
- A team is already deep into MVC conventions
- ApiExplorer / Swagger integration is non-negotiable
For pure JSON APIs in a green-field project, minimal APIs are faster to read, faster to ship, and easier to test.
Performance is roughly identical
Benchmarks since .NET 8 have closed the gap. The structural and developer-ergonomics arguments matter more than the 2% throughput difference.
Get the next issue
A short, curated email with the newest posts and questions.