Definition
ASP.NET Core Minimal APIs are a first-class way to define HTTP endpoints with less ceremony than MVC controllers — MapGet, MapPost, route groups, and automatic DI parameter binding. They are not a toy for demos. They are a valid production choice for thin gateways, webhooks, health checks, and vertical slices — when you organize endpoints into classes instead of dumping logic into Program.cs.
| Signal | Lean Minimal APIs | Lean controllers |
|---|---|---|
| Route count | Few (health, webhook, BFF slice) | Many actions on one Angular admin |
| Shared filters / conventions | One MapGroup + endpoint class | Base controller, action filters |
| Team familiarity | Greenfield slice or internal tool | Hiring loop expects MVC |
Food truck vs full kitchen: a few menu items, fast setup — not a banquet. You would not run a 40-action Angular admin from a food truck; you would not build a food truck inside a stadium kitchen either.
Routing
Minimal API basics and when to use → stay here.
Validation envelopes for Angular → API validation.
JWT policies and claims → ASP.NET Core JWT auth.
Layer boundaries and where logic lives → Clean Architecture.
Async patterns on endpoints → C# async/await in ASP.NET Core.
Architecture topic map → architecture hub.
Details
I use Minimal APIs for thin gateways, internal tools, and vertical slices. I keep controllers (or carefully organized endpoint classes) when Angular admin surfaces need many actions, filters, and shared conventions.
What Minimal APIs give you
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(...);
builder.Services.AddScoped<IOrderService, OrderService>();
var app = builder.Build();
app.MapGet("/api/health", () => Results.Ok(new { status = "ok" }));
app.MapGet("/api/orders/{id:guid}", async (
Guid id,
IOrderService orders,
CancellationToken ct) =>
{
var order = await orders.GetAsync(id, ct);
return order is null ? Results.NotFound() : Results.Ok(order);
});
app.Run();
DI parameters bind automatically. Return IResult helpers (Results.Ok, Results.NotFound, Results.ValidationProblem).
Minimal API in C# (Todo that would survive a PR)
A Minimal API in C# is MapGet / MapPost with DI parameter binding — not a new runtime. The sample people search for is a Todo list. The version I would merge still keeps HTTP in an endpoint class, validates input, and does not store state in a static List<T>.
public sealed record Todo(int Id, string Title, bool Done);
public sealed record CreateTodo(string Title);
public sealed class CreateTodoValidator : AbstractValidator<CreateTodo>
{
public CreateTodoValidator()
{
RuleFor(x => x.Title).NotEmpty().MaximumLength(200);
}
}
public static class TodoEndpoints
{
public static RouteGroupBuilder MapTodos(this WebApplication app)
{
var group = app.MapGroup("/api/todos");
group.MapGet("/", ListAsync);
group.MapGet("/{id:int}", GetByIdAsync);
group.MapPost("/", CreateAsync).AddEndpointFilter<FluentValidationFilter<CreateTodo>>();
return group;
}
private static async Task<IResult> ListAsync(ITodoStore store, CancellationToken ct)
=> Results.Ok(await store.ListAsync(ct));
private static async Task<IResult> GetByIdAsync(int id, ITodoStore store, CancellationToken ct)
{
var todo = await store.GetAsync(id, ct);
return todo is null ? Results.NotFound() : Results.Ok(todo);
}
private static async Task<IResult> CreateAsync(
CreateTodo body,
ITodoStore store,
CancellationToken ct)
{
var created = await store.AddAsync(body.Title, ct);
return Results.Created($"/api/todos/{created.Id}", created);
}
}
app.MapTodos();
When to use Minimal APIs: health, webhooks, a thin BFF, a vertical slice with a handful of routes.
When not to: a 40-action Angular admin with shared filters and a hiring funnel that already knows controllers. Also not: 40 Map* lambdas and EF queries in Program.cs. Handlers, validation, and JWT still exist — filter code: FluentValidation.
Organize so Program.cs does not become a landfill
// Endpoints/OrderEndpoints.cs
public static class OrderEndpoints
{
public static RouteGroupBuilder MapOrders(this WebApplication app)
{
var group = app.MapGroup("/api/orders").RequireAuthorization();
group.MapGet("/{id:guid}", GetByIdAsync);
group.MapPost("/", CreateAsync);
return group;
}
private static async Task<IResult> GetByIdAsync(
Guid id,
IOrderService orders,
CancellationToken ct)
{
var order = await orders.GetAsync(id, ct);
return order is null ? Results.NotFound() : Results.Ok(order);
}
}
app.MapOrders();
Same SRP rule as controllers: endpoints are HTTP adapters. Pricing and persistence stay in services/handlers.
Validation without MVC attributes everywhere
Options that work well:
- FluentValidation filters / endpoint filters
- DataAnnotations +
MiniValidator/ built-in validation where available - Explicit guard clauses for tiny endpoints
app.MapPost("/api/orders", async (
CreateOrderRequest request,
ICreateOrderHandler handler,
CancellationToken ct) =>
{
if (string.IsNullOrWhiteSpace(request.Sku))
return Results.ValidationProblem(new Dictionary<string, string[]>
{
["sku"] = ["SKU is required."]
});
var created = await handler.HandleAsync(request, ct);
return Results.Created($"/api/orders/{created.Id}", created);
});
Angular forms need stable problem details — same as MVC.
Auth, OpenAPI, and versioning
Swashbuckle vs built-in OpenAPI, Scalar, NSwag, and Angular codegen: Swagger vs OpenAPI in ASP.NET Core.
builder.Services.AddAuthentication().AddJwtBearer(...);
builder.Services.AddAuthorization();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
app.UseAuthentication();
app.UseAuthorization();
var orders = app.MapGroup("/api/v1/orders").RequireAuthorization("ManageOrders");
Minimal APIs play fine with JWT policies you already use for Angular SPAs.
Controllers vs Minimal APIs — honest tradeoffs
| Prefer Minimal APIs when… | Prefer Controllers when… |
|---|---|
| Few endpoints, clear vertical slices | Large admin API surface |
| Gateway / BFF / health / webhooks | Heavy filter pipelines and conventions already exist |
| Team likes function-style composition | Existing MVC codebase — rewrite cost > benefit |
| Prototypes that might stay small | Many shared base controller behaviors |
Ecom_NET10-style catalogs with dozens of merchandising actions still felt clearer as controllers. A CarBazaar webhook receiver was cleaner as Minimal API groups.
Performance note
Minimal APIs are lightweight, but your EF queries and auth dominate latency. Switching endpoint style will not fix N+1 SQL.
Failure story: everything in Program.cs
A team put 40 Map* calls, DTO mapping, and EF queries in one file "because Minimal APIs are simple." Code review became impossible; Angular contract changes took hours to find. We extracted endpoint classes and handlers — Minimal APIs stayed, the landfill left.
Delivery checklist
- Endpoints live in groups/classes — not a 2,000-line
Program.cs - Business logic in services/handlers, not lambdas
- JWT / policies applied via
RequireAuthorization - Validation returns consistent problem details for Angular
- OpenAPI covers the routes the SPA consumes
- Async +
CancellationTokenend to end - Decide controller vs Minimal API per bounded context, not as ideology
If an interviewer asks
"When would you choose Minimal APIs over controllers in ASP.NET Core?"
Strong answer: Minimal APIs for thin surfaces — gateways, webhooks, health checks, small vertical slices with few endpoints. Controllers when you have a large Angular admin with many actions, shared filters, and base-class conventions. Either way, business logic stays in services/handlers — not in Program.cs. Minimal APIs support DI, JWT via RequireAuthorization, validation via endpoint filters, and OpenAPI. The failure mode is a 2,000-line Program.cs, not Minimal APIs themselves.
Weak answer: "Minimal APIs are faster so use them everywhere."