Mert Özen Further With Every Line
Home Blog Controllers or Minimal APIs? When to Choose Which
Backend .NET Core Web API — #03

Controllers or Minimal APIs? When to Choose Which

Mert Özen Jul 25, 2026 10 min 3 views

We compare two ways of writing endpoints in .NET 9: controller-based structure and Minimal APIs. When each makes sense and what the real difference is, made clear with examples.

At the end of the previous article I made a small promise: we would compare two different ways of writing endpoints, namely the controller-based approach and Minimal APIs. That is exactly what we are doing today. But let me say this upfront: this is not a "winner-versus-loser" article. Both have places where they are the right call, and a big part of being a senior developer is turning the question "which one is better?" into "which one fits better here?"

Both Sit on the Same Foundation

Let's clear up the most common misconception first. A Minimal API is not a "lightweight" or "half" version of controllers. Both are built on the same ASP.NET Core foundation; they use the same routing infrastructure, the same dependency injection, the same middleware pipeline. The difference isn't in the engine itself, but in how you package that engine.

Think of it this way: same kitchen, same stove, same ingredients. The controller approach is like writing each recipe on its own card and storing it in a tidy box. A Minimal API is like writing the recipe straight into the notebook on the counter, so it stays right under your hand. The dish is the same dish; the only thing that changes is where you write the recipe and how much ceremony goes into it.

What Does the Controller-Based Structure Look Like?

The template we set up in the second article was already controller-based. In this approach, each group of endpoints lives in its own class. For example, a class holding user-related operations looks like this:

[ApiController]
[Route("users")]
public class UsersController : ControllerBase
{
    [HttpGet]
    public IActionResult GetAll()
    {
        return Ok(new[] { "Anna", "Tom" });
    }

    [HttpGet("{id}")]
    public IActionResult GetById(int id)
    {
        return Ok($"Kullanıcı {id}");
    }
}

A few things should have caught your eye here. The [Route("users")] at the top of the class says that all endpoints in this class will live under /users. The [HttpGet] and [HttpGet("{id}")] above the methods specify which HTTP method and which sub-path they respond to. As for [ApiController], it's the way to tell .NET "this is an API controller, hand me some conveniences automatically"; niceties like automatically catching model validation errors come along with it.

The most defining feature of this structure is order. Everything has its place: user operations in UsersController, product operations in ProductsController. A developer newly joining the project can guess where things are just by looking at the file name. As the project grows, that predictability becomes worth its weight in gold.

What Does a Minimal API Look Like?

Now let's write the same two endpoints with a Minimal API. Here we don't need a separate class; we define the endpoints directly inside Program.cs:

app.MapGet("/users", () =>
{
    return new[] { "Anna", "Tom" };
});

app.MapGet("/users/{id}", (int id) =>
{
    return $"Kullanıcı {id}";
});

Did you feel the difference? There's no class, no attribute, no ceremony. You say app.MapGet, you give the address, and you write whatever it should return. When you put the id parameter inside the method, .NET matches it with the {id} in the address on its own. Fewer lines, fewer files, a faster path to the point.

This simplicity offers a very pleasant experience, especially in small services. If you're writing a microservice that does a single job and consists of just a handful of endpoints, setting up a whole controller skeleton can feel like unnecessary weight. The Minimal API exists precisely for these situations.

So Is the Difference Only Cosmetic?

At first glance it looks like "one is just shorter to write," but the matter runs a little deeper. The real difference shows up in how the code behaves as the project grows.

Order and Scaling

When you have three endpoints, the Minimal API is wonderful. But once the endpoint count climbs to thirty or fifty, the Program.cs file starts turning into a giant list. When everything piles up in a single file, it becomes hard to read. There are, of course, ways to split a Minimal API into separate files too, but the moment you start doing that, you're essentially rebuilding by hand the order that controllers give you for free. The controller approach imposes that order by its very nature; while this sometimes feels like extra ceremony, in large projects it's an insurance policy.

Familiar Patterns

Controllers have been the backbone of the ASP.NET world for many years. That means the vast majority of examples on the internet, StackOverflow answers, and course material are controller-based. The fact that the code you'll run into when joining a new team is most likely to be controllers makes learning this approach a practical necessity. The Minimal API is relatively newer; it's spreading fast but doesn't yet have the same mature ocean of examples.

Performance

For a while, the claim "Minimal APIs are faster" was discussed a lot. There was a grain of truth to it, because in Minimal APIs some of the intermediate layers are thinner. But nowadays this difference is, most of the time, too small to measure within the total performance of a real application. So the sentence "I chose Minimal API to make it faster" isn't a very solid justification outside of very specific scenarios. You're far better off making your decision based on readability and ease of maintenance.

Then Which One Should I Choose?

Even though I'm not fond of hard rules, I can offer a practical compass for someone just starting out.

If you're in the learning phase and your goal is to grasp the common patterns of the .NET world, start with controllers. Because most of the code you'll encounter will look like this, and being able to read that structure will make life easier once you're on the job. In this series, too, we'll proceed mainly with the controller-based approach; that's exactly the reason.

If you're writing a small, single-purpose service, say a webhook receiver or a helper API with three endpoints, go with a Minimal API. You'll finish the job quickly without setting up unnecessary scaffolding.

If you're building a large, long-lived project that multiple teams will touch, the order controllers impose will give you peace of mind in the long run. It may feel like too many files at first, but six months later you'll be grateful for that order.

And don't forget this either: you can use both side by side in the same project. There's nothing wrong with keeping your core business logic in controllers while writing a one-line health-check endpoint with a Minimal API. These aren't mutually exclusive choices; they're two different tools in the same toolbox.

A Small Experiment

Before moving on to the next article, do this: to the project you set up in the second article, add both a Minimal API-style /ping endpoint and a small controller class containing a /status endpoint. Run both and call them from the browser. See with your own eyes that you've reached the same result by two different paths. This little experiment will settle the difference far more clearly than any paragraph could.

In the next article we start seriously writing our first real controller: we'll work through the subtleties of routing, what attributes are good for, and how action methods are put together, step by step. Keep this idea of "same foundation, different style" that you grasped today in a corner of your mind; that's where we'll pick up.