Minimal API Route Groups: Organize Your Endpoints Like a Pro

Minimal APIs are great until your Program.cs looks like a 400-line scroll of app.MapGet, app.MapPost, and app.MapDelete. At that point you start to miss controllers, not because they were better, but because they at least grouped things. Enter MapGroup(), which gives you clean organization AND endpoint filters for cross-cutting concerns, all without going back to controller-land.

Here’s how you group a set of related endpoints under a shared prefix:

var app = WebApplication.Create(args);
var todos = app.MapGroup("/api/todos")
.RequireAuthorization()
.AddEndpointFilter<RequestLoggingFilter>();
todos.MapGet("/", async (TodoDb db) =>
await db.Todos.ToListAsync());
todos.MapGet("/{id:int}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id) is { } todo
? Results.Ok(todo)
: Results.NotFound());
todos.MapPost("/", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/api/todos/{todo.Id}", todo);
});
app.Run();

Notice that RequireAuthorization() and AddEndpointFilter are applied to the group, not to each individual endpoint. Every route under /api/todos automatically gets auth and logging. No repetition, no forgetting to secure that one endpoint you added at 4:57 PM on a Friday.

Endpoint filters are the secret weapon here. They’re like middleware, but scoped to specific routes. Here’s a simple one that logs request timing:

public class RequestLoggingFilter(ILogger<RequestLoggingFilter> logger)
: IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext context,
EndpointFilterDelegate next)
{
var stopwatch = Stopwatch.StartNew();
var result = await next(context);
logger.LogInformation("Request completed in {Elapsed}ms",
stopwatch.ElapsedMilliseconds);
return result;
}
}

You can stack filters, nest groups inside groups, and even apply different auth policies per group. It’s all the organization of controllers with all the simplicity of minimal APIs. Best of both worlds: your Program.cs will finally be something you’re proud to show in a code review.