Minimal APIs vs Controllers: precisamos mesmo escolher?

Todo template novo de projeto ASP.NET Core empurra a mesma decisão para o início: Minimal API ou Controller. É uma escolha que parece definitiva, mas normalmente não precisa ser tratada como se fosse.

O que muda de fato entre os dois estilos

Minimal APIs definem endpoint com uma expressão curta, direto no Program.cs ou em arquivos de extensão organizados por módulo:

app.MapGet("/pedidos/{id}", async (int id, IPedidoService service) =>
{
    var pedido = await service.ObterPorIdAsync(id);
    return pedido is not null ? Results.Ok(pedido) : Results.NotFound();
});

Não existe classe de controller, não existe atributo de rota separado da definição do endpoint, e a injeção de dependência acontece direto nos parâmetros do delegate. Controllers seguem o modelo mais tradicional do ASP.NET Core: uma classe herdando de ControllerBase, com métodos de ação decorados por atributos de rota e verbo HTTP.

[ApiController]
[Route("pedidos")]
public class PedidosController(IPedidoService service) : ControllerBase
{
    [HttpGet("{id}")]
    public async Task<IActionResult> ObterPorId(int id)
    {
        var pedido = await service.ObterPorIdAsync(id);
        return pedido is not null ? Ok(pedido) : NotFound();
    }
}

Os dois trechos fazem exatamente a mesma coisa. A diferença está em organização, não em capacidade.

Minimal API deixou de ser “só para protótipo”

Existia uma percepção inicial de que Minimal APIs eram adequadas apenas para exemplos pequenos ou protótipos, sem recurso suficiente para aplicação real. Isso não reflete mais o estado atual do ASP.NET Core. Filtros de endpoint, via IEndpointFilter, permitem interceptar e modificar requisição e resposta de forma parecida com o que um filtro de action faz em Controllers. Agrupamento de rotas, via MapGroup, permite aplicar prefixo de rota, autorização e filtros a um conjunto de endpoints de uma vez, o que reduz bastante a repetição que Minimal APIs tinham no início.

Isso não significa que os dois estilos oferecem exatamente o mesmo ferramental ponto a ponto. Alguns recursos amadureceram primeiro no modelo de Controllers simplesmente por serem mais antigos, e certas convenções, como model binding mais elaborado envolvendo múltiplas fontes de dados, ainda tendem a ficar mais explícitas em Controllers.

Onde a concisão de Minimal APIs realmente ajuda

Minimal APIs tendem a compensar melhor em serviços pequenos e focados, como uma API dedicada a um domínio único, um backend para um serviço específico, ou qualquer cenário onde o número de endpoints é pequeno o suficiente para caber confortavelmente sem precisar de uma estrutura de classes por trás de cada rota. A ausência de camada de classe reduz a distância entre “olhar o código” e “entender o que aquele endpoint faz”.

Onde a estrutura de Controllers ainda compensa

Em projetos com muitos endpoints relacionados, agrupados por recurso, a estrutura de classe de um Controller organiza naturalmente ações que compartilham contexto, dependência e convenção de rota. Funcionalidades como filtros de ação, convenções de model binding mais ricas e um histórico maior de padrões documentados também pesam a favor de Controllers em times grandes, onde consistência entre desenvolvedores diferentes importa tanto quanto concisão individual.

A pergunta certa não é “qual escolher para o projeto inteiro”

Nada no ASP.NET Core impede os dois estilos de conviverem na mesma aplicação, cada um aplicado onde faz mais sentido. Um serviço pequeno e isolado pode ganhar com a simplicidade de uma Minimal API, enquanto um módulo maior, com múltiplos recursos relacionados, pode se beneficiar da organização que um Controller oferece.

Tratar essa decisão como uma escolha única e permanente para todo o projeto ignora que os dois modelos foram desenhados para o mesmo propósito, só com trade-offs diferentes de concisão contra estrutura. A pergunta mais útil não é “Minimal API ou Controller”, é “esse conjunto específico de endpoints se beneficia mais de que tipo de organização”.