Learnixo
Back to blog
AI Systemsintermediate

Minimal APIs vs Controllers — When to Choose Which

Honest comparison of Minimal APIs and MVC Controllers: performance, testability, organization at scale, team familiarity, and the framework signals for choosing one over the other in new and existing .NET projects.

Asma Hafeez KhanMay 16, 20264 min read
Minimal APIsControllersASP.NET Core.NETArchitecture
Share:š•

The Core Difference

MVC Controllers:
  Endpoint declared as class method
  Route: [HttpGet("{id}")] attribute
  Auth: [Authorize(Policy = "...")] attribute
  DI: constructor injection
  Filters: [ServiceFilter(typeof(MyFilter))] attribute
  Framework: action invoker pipeline, ModelState, IActionResult

Minimal APIs:
  Endpoint declared as delegate (lambda or method reference)
  Route: app.MapGet("{id}", handler)
  Auth: .RequireAuthorization("...")
  DI: parameter injection
  Filters: .AddEndpointFilter()
  Framework: lighter pipeline, IResult

Performance

Minimal API overhead per request: ~10-20% less than controllers
Why: No action invoker, no ControllerBase allocation, lighter pipeline

In practice:
  If your API processes 1,000 req/sec:
    20% savings = 200 req/sec improvement — significant
  If your API processes 10 req/sec:
    20% savings = 2 req/sec improvement — irrelevant

Recommendation: choose Minimal API for high-throughput APIs where
every millisecond matters. Choose controllers when productivity
and familiarity matter more than peak throughput.

Testability

C#
// Controller action test — requires full controller instantiation
public class PatientControllerTests
{
    [Fact]
    public async Task GetPatient_should_return_ok()
    {
        var controller = new PatientController(
            Substitute.For<IGetPatientHandler>());
        controller.ControllerContext = new ControllerContext
        {
            HttpContext = new DefaultHttpContext()
        };

        var result = await controller.GetPatient(Guid.NewGuid(), CancellationToken.None);
        result.Should().BeOfType<OkObjectResult>();
    }
}

// Minimal API handler test — just call the delegate
[Fact]
public async Task GetPatient_handler_should_return_not_found_for_unknown_id()
{
    var handler = Substitute.For<IGetPatientHandler>();
    handler.Handle(Arg.Any<GetPatientQuery>(), Arg.Any<CancellationToken>())
           .Returns(Result.Failure<PatientDto>(PatientErrors.NotFound));

    // Call the handler function directly — no HTTP context needed
    var result = await PatientEndpoints.GetPatient(
        Guid.NewGuid(), handler, CancellationToken.None);

    result.Should().BeOfType<NotFound>();
}

// Both work — WebApplicationFactory tests are equivalent for both

Organization at Scale

Minimal APIs — organizing 50+ endpoints:
  āœ“ MapGroup for shared prefix and auth
  āœ“ Extension methods (MapPatients, MapPrescriptions) split across files
  āœ— No built-in class-level organization
  āœ— Requires discipline to avoid Program.cs bloat

Controllers — organizing 50+ endpoints:
  āœ“ Each controller is a class — natural grouping
  āœ“ [ApiController] + [Route("api/[controller]")] gives consistent URLs
  āœ“ Familiar to every .NET developer
  āœ— Heavier — ControllerBase, ModelState, ActionResult hierarchy
  āœ— Attributes scattered through the file

Side-by-Side Comparison

Feature                  Minimal API         Controller
─────────────────────────────────────────────────────────
Performance              Faster              Slightly slower
Boilerplate              Less                More (class, constructor)
Discoverability          Requires discipline Easy (class browser)
Routing                  Fluent              Attributes
Auth                     .RequireAuthorization [Authorize]
Filters                  .AddEndpointFilter   [ServiceFilter]
Team onboarding          Newer pattern        Universal familiarity
Complex binding          Manual              ModelState auto-binding
File organization        Extension methods    Classes
OpenAPI                  WithOpenApi()        Automatic from attributes

Decision Framework

Choose Minimal APIs when:
  āœ“ Performance is a top concern (microservices, high-throughput)
  āœ“ Starting a new project with a team familiar with Minimal APIs
  āœ“ Building a small-to-medium API (under 50 endpoints) where flat organization works
  āœ“ You want a clean architecture without MVC's weight

Choose Controllers when:
  āœ“ Team is large with developers of varying .NET experience
  āœ“ Existing MVC codebase — consistency matters more than performance
  āœ“ Complex model binding with ModelState is heavily used
  āœ“ Large number of endpoints where class-based organization is clearer
  āœ“ You are adding a few endpoints to an existing MVC project

Both are fine:
  Mixing both in one project is supported and common during migration
  Controllers for legacy endpoints; Minimal APIs for new features

Migration Pattern

C#
// Migrate a controller endpoint to Minimal API incrementally
// Step 1: Controller still exists
[ApiController]
[Route("api/[controller]")]
public class PatientsController : ControllerBase
{
    [HttpGet("{id}")]
    public async Task<IActionResult> Get(Guid id, [FromServices] IGetPatientHandler handler)
        => (await handler.Handle(new GetPatientQuery(id), HttpContext.RequestAborted))
           .Match<IActionResult>(dto => Ok(dto), err => err.ToActionResult());
}

// Step 2: Add equivalent Minimal API (test both work)
app.MapGet("/api/patients/{id:guid}", async (
    Guid id, IGetPatientHandler handler, CancellationToken ct) =>
{
    var result = await handler.Handle(new GetPatientQuery(id), ct);
    return result.Match(Results.Ok, err => err.ToProblemResult());
}).RequireAuthorization();

// Step 3: Remove the controller action
// Step 4: Remove the controller class when all endpoints are migrated

Red Flag / Green Answer

Red Flag: "We are building a new project and choosing between controllers and Minimal APIs based on what we find in online tutorials."

Choose based on team context, not tutorials. If the team knows controllers well and the API is growing large, controllers provide familiar structure. If you are building a high-performance service or the team is comfortable with the pattern, Minimal APIs reduce overhead.

Green Answer:

Evaluate: team familiarity, API size, performance requirements, existing codebase consistency. Document the decision. Both are production-quality choices — the criteria matter, not a universal "Minimal APIs are better."


Key Takeaway

Minimal APIs are faster and lighter with less boilerplate. Controllers provide class-based organization familiar to all .NET developers. For new projects, Minimal APIs with MapGroup and extension methods is the direction Microsoft is investing in. For existing codebases or large teams, controllers remain a solid choice. The difference matters at scale — choose based on your actual context, not framework hype.

Enjoyed this article?

Explore the AI Systems learning path for more.

Found this helpful?

Share:š•

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.