Minimal APIs vs Controllers
Overview
ASP.NET Core offers two primary approaches for building Web APIs:
- Minimal APIs (introduced in .NET 6, enhanced in .NET 7+)
- Controller-based APIs (traditional MVC pattern)
Minimal APIs
What Are They?
Minimal APIs provide a simplified approach to building HTTP APIs with minimal dependencies, configuration, and ceremony.
Basic Example
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/api/products", () =>
{
return new[] { "Product 1", "Product 2", "Product 3" };
});
app.MapPost("/api/products", (Product product) =>
{
// Save product
return Results.Created($"/api/products/{product.Id}", product);
});
app.Run();
record Product(int Id, string Name, decimal Price);
Key Features
- Concise syntax: Less boilerplate code
- Functional approach: Lambda expressions for route handlers
- Fast startup: Reduced memory footprint
- Top-level statements: No need for Main method or Startup class
- Source generators: Better performance
When to Use Minimal APIs
✅ Good for:
- Microservices
- Simple APIs with few endpoints
- Serverless functions
- Prototypes and quick demos
- APIs that prioritize performance
❌ Not ideal for:
- Large monolithic applications
- Complex business logic requiring many filters/attributes
- Teams preferring class-based organization
- When you need extensive middleware per endpoint
Controller-Based APIs
Basic Example
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
{
_productService = productService;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
{
var products = await _productService.GetAllAsync();
return Ok(products);
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateProduct(Product product)
{
var created = await _productService.CreateAsync(product);
return CreatedAtAction(nameof(GetProduct), new { id = created.Id }, created);
}
[HttpGet("{id}")]
public async Task<ActionResult<Product>> GetProduct(int id)
{
var product = await _productService.GetByIdAsync(id);
if (product == null)
return NotFound();
return Ok(product);
}
}
Key Features
- Class-based organization: Logical grouping of related endpoints
- Action filters: Extensive filter pipeline
- Model binding: Automatic parameter binding from multiple sources
- Convention-based routing: Attribute routing and convention routing
- Built-in features: Model validation, content negotiation, etc.
When to Use Controllers
✅ Good for:
- Large applications with many endpoints
- Complex validation and authorization logic
- Teams familiar with MVC patterns
- When you need extensive action filters
- Applications requiring OpenAPI/Swagger customization
Comparison Table
| Aspect | Minimal APIs | Controllers |
|---|---|---|
| Syntax | Functional, concise | Class-based, verbose |
| Routing | Route groups, lambda handlers | Attribute routing, convention routing |
| Filters | Endpoint filters | Action filters, authorization filters |
| Organization | File-based or grouped | Controller classes |
| Learning Curve | Lower | Higher |
| Performance | Slightly faster | Very good |
| Startup Time | Faster | Good |
| Memory Footprint | Lower | Slightly higher |
| Best For | Microservices, simple APIs | Complex applications |
Mixing Both Approaches
You can use both in the same application:
var builder = WebApplication.CreateBuilder(args);
// Register controllers
builder.Services.AddControllers();
var app = builder.Build();
// Minimal API endpoints
app.MapGet("/health", () => Results.Ok("Healthy"));
// Controller endpoints
app.MapControllers();
app.Run();
Modern Patterns (ASP.NET Core 7+)
Route Groups (Minimal APIs)
var productsGroup = app.MapGroup("/api/products")
.WithTags("Products")
.RequireAuthorization();
productsGroup.MapGet("/", GetAllProducts);
productsGroup.MapGet("/{id}", GetProduct);
productsGroup.MapPost("/", CreateProduct);
Endpoint Filters (Minimal APIs)
app.MapGet("/api/products/{id}", GetProduct)
.AddEndpointFilter(async (context, next) =>
{
var id = context.GetArgument<int>(0);
if (id <= 0)
return Results.BadRequest("Invalid ID");
return await next(context);
});
Interview Questions
Q: When would you choose Minimal APIs over Controllers? A: Minimal APIs are ideal for microservices, simple APIs, prototypes, and scenarios where performance and startup time are critical. They reduce boilerplate and work well with functional programming patterns.
Q: Can you use both approaches in one application? A: Yes, ASP.NET Core allows mixing Minimal APIs and Controllers in the same application, which is useful for migrating gradually or using each approach where it fits best.
Q: What are the performance differences? A: Minimal APIs have slightly better performance due to less overhead and source generator optimizations, but the difference is minimal for most applications. Both approaches are highly performant.