Minimal API no .NET

Rafael Coelho
· 8 min de leitura

Minimal API é uma forma de construir APIs HTTP no .NET sem precisar de controllers, sem Startup.cs, sem atributos decorando métodos e sem aquela estrutura de pastas que todo mundo já conhece do MVC.

Ela foi introduzida no .NET 6 e a ideia é simples: você define suas rotas, seus handlers e suas configurações — tudo num único arquivo, se quiser.

Não é um framework novo. Não é uma lib externa. É o próprio ASP.NET Core, só que com uma superfície menor pra você começar.


Por que ela existe?

O ASP.NET Core com controllers funciona bem. Mas pra muita coisa, ele é mais estrutura do que você precisa.

Imagina que você quer criar uma API com 3 endpoints. Com o modelo tradicional, você precisa:

  • Criar um projeto com template webapi
  • Ter um Program.cs configurando o host
  • Ter um Startup.cs registrando serviços e middlewares
  • Criar uma pasta Controllers/
  • Criar uma classe que herda de ControllerBase
  • Decorar com [ApiController], [Route], [HttpGet]...

Pra 3 endpoints. É muita cerimônia.

A Minimal API resolve isso. Você vai direto ao ponto.


Hello World em 4 linhas

Sem exagero:

csharp
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/", () => "Hello World!"); app.Run();

Isso aqui já é uma API rodando. Sem controller, sem atributo, sem herança de classe.

O WebApplication.CreateBuilder configura o host, o logging, as variáveis de ambiente — tudo que você espera de um setup padrão do ASP.NET Core. O builder.Build() cria a aplicação. E o MapGet registra a rota.

Pronto. Rodou.


Entendendo a estrutura

Antes de sair criando endpoints, vale entender o que cada parte faz.

Builder

csharp
var builder = WebApplication.CreateBuilder(args);

O builder é onde você configura serviços (injeção de dependência, banco de dados, autenticação, etc.) e opções da aplicação (logging, configuration, etc.).

Tudo que você faria no Startup.ConfigureServices do modelo antigo, faz aqui.

App

csharp
var app = builder.Build();

O app é onde você configura o pipeline HTTP — middlewares, rotas, tratamento de erros.

Tudo que você faria no Startup.Configure, faz aqui.

Mapeamento de rotas

csharp
app.MapGet("/rota", () => "resposta"); app.MapPost("/rota", (Dto body) => { /* ... */ }); app.MapPut("/rota/{id}", (int id, Dto body) => { /* ... */ }); app.MapDelete("/rota/{id}", (int id) => { /* ... */ });

Cada método Map* recebe a rota e um handler — que pode ser uma lambda, um método estático ou um delegate qualquer.

Simples assim.


Model Binding automático

Uma das coisas mais legais da Minimal API é que o model binding funciona por convenção.

O runtime olha pros parâmetros do seu handler e resolve sozinho:

  • Veio na rota? → Mapeia do path ({id})
  • Veio na query string? → Mapeia automaticamente
  • É um objeto complexo? → Assume que veio no body (JSON)
  • É um serviço registrado? → Injeta via DI
csharp
app.MapPost("/produtos", (Produto produto) => { // "produto" veio do body, deserializado automaticamente return Results.Created($"/produtos/{produto.Id}", produto); }); app.MapGet("/produtos/{id}", (int id) => { // "id" veio da rota return Results.Ok(id); }); app.MapGet("/produtos", (string? nome, int? pagina) => { // "nome" e "pagina" vieram da query string return Results.Ok(); });

Sem [FromBody], sem [FromRoute], sem [FromQuery].

Você pode usar esses atributos se quiser ser explícito, mas na maioria dos casos não precisa.


Retornando respostas corretamente

Você pode retornar qualquer coisa de um handler — uma string, um objeto, um status code.

Mas pra ter controle sobre o status code e os headers, usa a classe Results:

csharp
// 200 com corpo Results.Ok(objeto) // 201 com header Location Results.Created($"/recurso/{id}", objeto) // 204 sem corpo Results.NoContent() // 404 Results.NotFound() // 400 com detalhes Results.BadRequest("mensagem de erro") // 401 Results.Unauthorized()

Isso é equivalente aos return Ok(), return NotFound() que você usaria numa controller. Mesma coisa, sintaxe diferente.


Injeção de dependência

Funciona exatamente igual ao MVC.

Você registra o serviço no builder:

csharp
builder.Services.AddScoped<IProdutoService, ProdutoService>(); builder.Services.AddSingleton<ICache, RedisCache>(); builder.Services.AddTransient<INotificador, EmailNotificador>();

E recebe no handler como parâmetro:

csharp
app.MapGet("/produtos", (IProdutoService service) => { return Results.Ok(service.ListarTodos()); });

O runtime resolve a dependência automaticamente. Sem [FromServices], sem construtor, sem nada.

Se você registrou, ele injeta.


Validação

A Minimal API não tem o [ApiController] que faz validação automática de ModelState. Mas você tem algumas opções boas:

Validação manual

csharp
app.MapPost("/produtos", (Produto produto) => { if (string.IsNullOrEmpty(produto.Nome)) return Results.BadRequest("Nome é obrigatório"); if (produto.Preco <= 0) return Results.BadRequest("Preço precisa ser maior que zero"); return Results.Created($"/produtos/{produto.Id}", produto); });

Com FluentValidation

csharp
builder.Services.AddScoped<IValidator<Produto>, ProdutoValidator>(); app.MapPost("/produtos", (Produto produto, IValidator<Produto> validator) => { var result = validator.Validate(produto); if (!result.IsValid) return Results.BadRequest(result.Errors); return Results.Created($"/produtos/{produto.Id}", produto); });

Com Endpoint Filters (a partir do .NET 7)

csharp
app.MapPost("/produtos", (Produto produto) => { return Results.Created($"/produtos/{produto.Id}", produto); }) .AddEndpointFilter<ValidationFilter<Produto>>();

Os Endpoint Filters funcionam como um middleware específico pra aquela rota. Dá pra usar pra validação, logging, autenticação customizada — o que precisar.


Middlewares

Todo middleware do ASP.NET Core funciona na Minimal API. Sem exceção.

csharp
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); // CORS builder.Services.AddCors(); app.UseCors(policy => policy.AllowAnyOrigin()); // Autenticação + Autorização builder.Services.AddAuthentication().AddJwtBearer(); builder.Services.AddAuthorization(); app.UseAuthentication(); app.UseAuthorization(); // Rota protegida app.MapGet("/admin", () => "área restrita") .RequireAuthorization(); // Rota pública app.MapGet("/health", () => Results.Ok("alive")); app.Run();

O pipeline é o mesmo. A única diferença é que em vez de configurar num Startup.Configure, você configura inline.


Organizando quando cresce

A crítica mais comum à Minimal API é: "beleza, funciona pra Hello World, mas e quando eu tenho 50 rotas?"

Justo. Colocar 50 MapGet num Program.cs gigante é horrível.

A solução é agrupar rotas com extension methods:

csharp
// Program.cs var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapProdutoEndpoints(); app.MapUsuarioEndpoints(); app.MapPedidoEndpoints(); app.Run();
csharp
// Endpoints/ProdutoEndpoints.cs public static class ProdutoEndpoints { public static void MapProdutoEndpoints(this WebApplication app) { var group = app.MapGroup("/produtos"); group.MapGet("/", ListarTodos); group.MapGet("/{id}", BuscarPorId); group.MapPost("/", Criar); group.MapPut("/{id}", Atualizar); group.MapDelete("/{id}", Deletar); } private static IResult ListarTodos(IProdutoService service) => Results.Ok(service.ListarTodos()); private static IResult BuscarPorId(int id, IProdutoService service) => Results.Ok(service.BuscarPorId(id)); // ... }

O MapGroup (disponível a partir do .NET 7) agrupa rotas com um prefixo comum. Dá pra aplicar filtros, autenticação e metadados no grupo inteiro.

Com essa abordagem, cada arquivo cuida das suas rotas. O Program.cs fica limpo. E você tem a organização de controllers sem a cerimônia de controllers.


Minimal API vs Controllers: quando usar cada um?

Não é uma questão de qual é melhor. É questão de contexto.

Minimal API brilha quando:

  • O projeto é pequeno ou médio
  • Você tá num time enxuto (1 a 4 devs)
  • É um microsserviço com escopo definido
  • É um BFF, uma edge function ou serverless
  • Você quer prototipar rápido

Controllers fazem mais sentido quando:

  • O projeto tem dezenas de módulos e centenas de rotas
  • O time é grande e precisa de uma convenção rígida
  • Você precisa de features como [ApiController] com validação automática
  • O projeto já existe e usa controllers — não tem motivo pra migrar

O ponto principal é: a Minimal API não é uma versão limitada. Ela tem acesso a tudo que o ASP.NET Core oferece. A diferença é que ela não te obriga a seguir uma estrutura específica.

E justamente por isso, em times grandes, controllers podem ser uma escolha melhor — porque a estrutura imposta vira uma vantagem quando todo mundo precisa seguir o mesmo padrão.


Performance

Um detalhe que pouca gente fala: a Minimal API é mais rápida que controllers.

Não por uma margem absurda, mas ela tem menos overhead. Sem reflexão pra descobrir rotas, sem ativação de controllers, sem action filters sendo resolvidos a cada request.

Pra maioria das aplicações, a diferença é irrelevante. Mas pra cenários de alta throughput ou serverless onde cada milissegundo conta, é um ponto a favor.

E combinando com AOT (Ahead of Time compilation) do .NET 9, você consegue binários nativos com startup instantâneo e consumo de memória mínimo. Ideal pra containers e cold starts.


Exemplo completo: API de produtos

Pra fechar, um exemplo mais realista juntando tudo:

csharp
var builder = WebApplication.CreateBuilder(args); // Serviços builder.Services.AddScoped<IProdutoService, ProdutoService>(); builder.Services.AddCors(); var app = builder.Build(); // Middlewares app.UseCors(p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); // Rotas var produtos = app.MapGroup("/api/produtos"); produtos.MapGet("/", (IProdutoService service) => Results.Ok(service.ListarTodos())); produtos.MapGet("/{id}", (int id, IProdutoService service) => { var produto = service.BuscarPorId(id); return produto is not null ? Results.Ok(produto) : Results.NotFound(); }); produtos.MapPost("/", (Produto produto, IProdutoService service) => { if (string.IsNullOrEmpty(produto.Nome)) return Results.BadRequest("Nome é obrigatório"); var criado = service.Criar(produto); return Results.Created($"/api/produtos/{criado.Id}", criado); }); produtos.MapPut("/{id}", (int id, Produto produto, IProdutoService service) => { var atualizado = service.Atualizar(id, produto); return atualizado is not null ? Results.Ok(atualizado) : Results.NotFound(); }); produtos.MapDelete("/{id}", (int id, IProdutoService service) => { service.Deletar(id); return Results.NoContent(); }); app.Run();

DI, CORS, validação, status codes corretos, agrupamento de rotas. Tudo num arquivo, tudo legível.


Conclusão

Minimal API não é brinquedo e não é atalho. É uma forma legítima — e muitas vezes mais adequada — de construir APIs no .NET.

Ela te dá tudo que o ASP.NET Core oferece, sem te obrigar a montar uma estrutura que você não precisa. E quando o projeto cresce, dá pra organizar com MapGroup e extension methods sem perder a simplicidade.

Se você nunca testou, cria um projeto com dotnet new web e brinca um pouco. Em 10 minutos você já vai ter uma API rodando.