Minimal APIs vs Controller-Based APIs
ASP.NET Core offers two styles: Controller-based (class inheriting ControllerBase, attributes) and Minimal APIs (inline route handlers in Program.cs). Controllers are better for complex APIs with filters, versioning, and Swagger. Minimal APIs are better for microservices and simple endpoints.
Controllers vs Minimal APIs — When to Use Each
// ━━ Controller-based API — use for complex APIs ━━
[ApiController] // enables automatic model validation errors
[Route("api/v{version:apiVersion}/[controller]")]
[Produces("application/json")]
public class ProductsController(
IProductService service,
ILogger<ProductsController> logger) : ControllerBase
{
[HttpGet]
[ProducesResponseType<PagedResult<ProductDto>>(200)]
[ProducesResponseType(400)]
public async Task<ActionResult<PagedResult<ProductDto>>> GetAll(
[FromQuery] ProductFilter filter,
CancellationToken ct) =>
Ok(await service.GetPagedAsync(filter, ct));
[HttpGet("{id:guid}")]
[ProducesResponseType<ProductDto>(200)]
[ProducesResponseType(404)]
public async Task<ActionResult<ProductDto>> GetById(Guid id, CancellationToken ct)
{
var product = await service.GetByIdAsync(id, ct);
return product is null ? NotFound() : Ok(product);
}
}
// ━━ Minimal API — use for simple microservice endpoints ━━
// Faster startup, less overhead, no controller attributes
var products = app.MapGroup("/api/products").RequireAuthorization();
products.MapGet("/", async (IProductService svc, CancellationToken ct) =>
Results.Ok(await svc.GetAllAsync(ct)));
products.MapGet("/{id:guid}", async (Guid id, IProductService svc, CancellationToken ct) =>
{
var product = await svc.GetByIdAsync(id, ct);
return product is null ? Results.NotFound() : Results.Ok(product);
});
products.MapPost("/", async (
CreateProductRequest req, // auto-bound from body
IProductService svc,
CancellationToken ct) =>
{
var result = await svc.CreateAsync(req, ct);
return Results.Created($"/api/products/{result.Id}", result);
});
// Placeholder types
interface IProductService
{
Task<dynamic> GetPagedAsync(dynamic f, CancellationToken ct);
Task<dynamic?> GetByIdAsync(Guid id, CancellationToken ct);
Task<dynamic> GetAllAsync(CancellationToken ct);
Task<dynamic> CreateAsync(dynamic r, CancellationToken ct);
}
record PagedResult<T>(System.Collections.Generic.List<T> Items, int Total);
record ProductDto(Guid Id, string Name);
record ProductFilter(string? Category, int Page = 1, int PageSize = 20);
record CreateProductRequest(string Name, decimal Price);Tip
Tip
Practice Minimal APIs vs ControllerBased APIs in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
.NET provides a unified platform for building various application types
Practice Task
Note
Practice Task — (1) Write a working example of Minimal APIs vs ControllerBased APIs from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Quick Quiz
Common Mistake
Warning
A common mistake with Minimal APIs vs ControllerBased APIs is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready dotnet code.