Skip to main content

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

AspectMinimal APIsControllers
SyntaxFunctional, conciseClass-based, verbose
RoutingRoute groups, lambda handlersAttribute routing, convention routing
FiltersEndpoint filtersAction filters, authorization filters
OrganizationFile-based or groupedController classes
Learning CurveLowerHigher
PerformanceSlightly fasterVery good
Startup TimeFasterGood
Memory FootprintLowerSlightly higher
Best ForMicroservices, simple APIsComplex 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.

Resources