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.csconfigurando o host - Ter um
Startup.csregistrando 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:
1var builder = WebApplication.CreateBuilder(args); 2var app = builder.Build(); 3 4app.MapGet("/", () => "Hello World!"); 5 6app.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
1var 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
1var 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
1app.MapGet("/rota", () => "resposta"); 2app.MapPost("/rota", (Dto body) => { /* ... */ }); 3app.MapPut("/rota/{id}", (int id, Dto body) => { /* ... */ }); 4app.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
1app.MapPost("/produtos", (Produto produto) => 2{ 3 // "produto" veio do body, deserializado automaticamente 4 return Results.Created($"/produtos/{produto.Id}", produto); 5}); 6 7app.MapGet("/produtos/{id}", (int id) => 8{ 9 // "id" veio da rota 10 return Results.Ok(id); 11}); 12 13app.MapGet("/produtos", (string? nome, int? pagina) => 14{ 15 // "nome" e "pagina" vieram da query string 16 return Results.Ok(); 17});
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:
1// 200 com corpo 2Results.Ok(objeto) 3 4// 201 com header Location 5Results.Created($"/recurso/{id}", objeto) 6 7// 204 sem corpo 8Results.NoContent() 9 10// 404 11Results.NotFound() 12 13// 400 com detalhes 14Results.BadRequest("mensagem de erro") 15 16// 401 17Results.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:
1builder.Services.AddScoped<IProdutoService, ProdutoService>(); 2builder.Services.AddSingleton<ICache, RedisCache>(); 3builder.Services.AddTransient<INotificador, EmailNotificador>();
E recebe no handler como parâmetro:
1app.MapGet("/produtos", (IProdutoService service) => 2{ 3 return Results.Ok(service.ListarTodos()); 4});
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
1app.MapPost("/produtos", (Produto produto) => 2{ 3 if (string.IsNullOrEmpty(produto.Nome)) 4 return Results.BadRequest("Nome é obrigatório"); 5 6 if (produto.Preco <= 0) 7 return Results.BadRequest("Preço precisa ser maior que zero"); 8 9 return Results.Created($"/produtos/{produto.Id}", produto); 10});
Com FluentValidation
1builder.Services.AddScoped<IValidator<Produto>, ProdutoValidator>(); 2 3app.MapPost("/produtos", (Produto produto, IValidator<Produto> validator) => 4{ 5 var result = validator.Validate(produto); 6 7 if (!result.IsValid) 8 return Results.BadRequest(result.Errors); 9 10 return Results.Created($"/produtos/{produto.Id}", produto); 11});
Com Endpoint Filters (a partir do .NET 7)
1app.MapPost("/produtos", (Produto produto) => 2{ 3 return Results.Created($"/produtos/{produto.Id}", produto); 4}) 5.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.
1var builder = WebApplication.CreateBuilder(args); 2var app = builder.Build(); 3 4// CORS 5builder.Services.AddCors(); 6app.UseCors(policy => policy.AllowAnyOrigin()); 7 8// Autenticação + Autorização 9builder.Services.AddAuthentication().AddJwtBearer(); 10builder.Services.AddAuthorization(); 11app.UseAuthentication(); 12app.UseAuthorization(); 13 14// Rota protegida 15app.MapGet("/admin", () => "área restrita") 16 .RequireAuthorization(); 17 18// Rota pública 19app.MapGet("/health", () => Results.Ok("alive")); 20 21app.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:
1// Program.cs 2var builder = WebApplication.CreateBuilder(args); 3var app = builder.Build(); 4 5app.MapProdutoEndpoints(); 6app.MapUsuarioEndpoints(); 7app.MapPedidoEndpoints(); 8 9app.Run();
1// Endpoints/ProdutoEndpoints.cs 2public static class ProdutoEndpoints 3{ 4 public static void MapProdutoEndpoints(this WebApplication app) 5 { 6 var group = app.MapGroup("/produtos"); 7 8 group.MapGet("/", ListarTodos); 9 group.MapGet("/{id}", BuscarPorId); 10 group.MapPost("/", Criar); 11 group.MapPut("/{id}", Atualizar); 12 group.MapDelete("/{id}", Deletar); 13 } 14 15 private static IResult ListarTodos(IProdutoService service) 16 => Results.Ok(service.ListarTodos()); 17 18 private static IResult BuscarPorId(int id, IProdutoService service) 19 => Results.Ok(service.BuscarPorId(id)); 20 21 // ... 22}
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:
1var builder = WebApplication.CreateBuilder(args); 2 3// Serviços 4builder.Services.AddScoped<IProdutoService, ProdutoService>(); 5builder.Services.AddCors(); 6 7var app = builder.Build(); 8 9// Middlewares 10app.UseCors(p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); 11 12// Rotas 13var produtos = app.MapGroup("/api/produtos"); 14 15produtos.MapGet("/", (IProdutoService service) => 16 Results.Ok(service.ListarTodos())); 17 18produtos.MapGet("/{id}", (int id, IProdutoService service) => 19{ 20 var produto = service.BuscarPorId(id); 21 return produto is not null 22 ? Results.Ok(produto) 23 : Results.NotFound(); 24}); 25 26produtos.MapPost("/", (Produto produto, IProdutoService service) => 27{ 28 if (string.IsNullOrEmpty(produto.Nome)) 29 return Results.BadRequest("Nome é obrigatório"); 30 31 var criado = service.Criar(produto); 32 return Results.Created($"/api/produtos/{criado.Id}", criado); 33}); 34 35produtos.MapPut("/{id}", (int id, Produto produto, IProdutoService service) => 36{ 37 var atualizado = service.Atualizar(id, produto); 38 return atualizado is not null 39 ? Results.Ok(atualizado) 40 : Results.NotFound(); 41}); 42 43produtos.MapDelete("/{id}", (int id, IProdutoService service) => 44{ 45 service.Deletar(id); 46 return Results.NoContent(); 47}); 48 49app.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.