Minimal APIs in ASP.NET Core make it possible to build HTTP APIs with very little boilerplate.
But there is a common misunderstanding:
«Minimal API does not mean minimal architecture.»
When developers start using Minimal APIs, it is tempting to put everything inside Program.cs.
At first, this looks perfectly fine:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/api/company", async (CompanyDbContext db) =>
{
var company = await db.Companies.FirstOrDefaultAsync();
return company is null
? Results.NotFound()
: Results.Ok(company);
});
app.Run();
For a small API with a few endpoints, this can work. But as the application grows, Program.cs can quickly become bloated and difficult to maintain.
Minimal does not mean everything in one file
The goal of Minimal APIs is to reduce unnecessary ceremony around HTTP endpoints. It does not mean that every responsibility should live in the same place.
A better approach is to keep the endpoint routing simple while separating application responsibilities.
For example:
CompanyProfile
│
├── API
│ └── Endpoints
│ └── CompanyEndpoints.cs
│
├── Infrastructure
│ └── Data
│ └── CompanyDbContext.cs
│
├── Core
| └── DTOs
| └── Entities
│
└── Program.cs
Now Program.cs can focus strictly on application configuration and startup.
Moving endpoints out of Program.cs (Separating Endpoints from Handlers)
Instead of writing inline lambdas inside the routing methods, we can separate the Endpoint Mapping from the Handler Logic.
This keeps our routes declarative and organized:
public static class CompanyEndpoints
{
public static void MapCompanyEndpoints(this IEndpointRouteBuilder app)
{
var publicGroup = app.MapGroup("/api").WithTags("Public Website API");
publicGroup.MapGet("/info", GetCompanyInfo)
.WithSummary("Get Company Information");
}
// Explicit Handler Function
private static async Task<IResult> GetCompanyInfo(AppDbContext db)
{
var info = await db.CompanyInformation.FirstOrDefaultAsync();
return info is null
? Results.NotFound()
: Results.Ok(new CompanyInfoResponseDto(info.Name, info.Description, info.Vision, info.Mission));
}
}
Why separate the Handler?
- Clean Route Definitions: Route mappings read like a table of contents without noise.
- Better Testability: Handlers can be invoked or unit-tested directly without setting up full HTTP pipelines.
- Reusability & Readability: Keeps routing methods concise and clean.
Then register the endpoints cleanly from Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services...
var app = builder.Build();
app.MapCompanyEndpoints();
app.Run();
Why separate endpoints?
As the number of endpoints increases, organization becomes critical. Instead of having a single monolithic file, group related endpoints together:
Endpoints
├── CompanyEndpoints.cs
├── ExampleEndpoints.cs
└── ExampleEndpoints2.cs
Now each endpoint module has a single responsibility.
Minimal APIs and Dependency Injection
Minimal APIs work naturally with ASP.NET Core's Dependency Injection system. Dependencies can be injected directly into handler parameters:
private static async Task<IResult> GetServices(AppDbContext db)
{
var services = await db.Services
.Select(s => new ServiceResponseDto(s.Id, s.Title, s.Description, s.Icon))
.ToListAsync();
return Results.Ok(services);
}
There is no need to manually instantiate services—ASP.NET Core resolves them via DI per request scope.
Minimal APIs with Entity Framework Core
When working with EF Core inside handlers, always aim for efficient queries by leveraging features like AsNoTracking() for read-only endpoints:
private static async Task<IResult> GetServices(AppDbContext db)
{
var services = await db.Services
.AsNoTracking()
.Select(s => new ServiceResponseDto(s.Id, s.Title, s.Description, s.Icon))
.ToListAsync();
return Results.Ok(services);
}
For larger domain logic, you can seamlessly delegate tasks to dedicated service layers or repositories when application complexity demands it.
What about Controllers?
Minimal APIs are not necessarily a replacement for Controllers. Both approaches have their place.
Minimal APIs excel when you want:
- Reduced boilerplate
- Lightweight HTTP endpoints
- Microservices & lightweight background modules
- Performance-focused, endpoint-centric routing
Controllers remain valuable when:
- Working with legacy MVC applications
- Relying heavily on controller-level action filters and complex conventions
The question isn't "Which one is better?", but rather "Which approach fits the system I am building?"
The Important Distinction
Minimal API
↓
Minimal endpoint ceremony
Minimal architecture
↓
Everything squeezed into Program.cs
The first is a modern design feature. The second is an architectural anti-pattern.
A Practical Example
I applied this structure while building a clean Company Profile API using:
- ASP.NET Core Minimal APIs
- Entity Framework Core
- Dependency Injection
- OpenAPI / Scalar UI
- Modular Endpoint Organization
🔗 GitHub Repository: NoofSaeed/CompanyProfile
Final Thoughts
Minimal APIs are one of the most powerful additions to modern .NET. Their simplicity is valuable, but simplicity should not be confused with lack of structure.
A maintainable Minimal API relies on:
- Clean, decoupled Handlers
- Modular Endpoint Registration
- Dependency Injection
- Well-structured Project Layout
The syntax stays minimal while the architecture remains scalable.
Top comments (0)