Minimal API no .NET

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:
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
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
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
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
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:
// 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:
builder.Services.AddScoped<IProdutoService, ProdutoService>();
builder.Services.AddSingleton<ICache, RedisCache>();
builder.Services.AddTransient<INotificador, EmailNotificador>();E recebe no handler como parâmetro:
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
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
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)
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.
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:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapProdutoEndpoints();
app.MapUsuarioEndpoints();
app.MapPedidoEndpoints();
app.Run();// 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:
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.