Building High-Performance Web APIs with C# Minimal APIs in .NET

Why Choose Minimal APIs?
Traditional ASP.NET Core controllers provide a lot of features—model binding, filters, attribute routing, and more. However, this richness comes with a certain amount of overhead:
- Startup complexity: Controllers require folder structures,
[ApiController]attributes, and sometimes verbose configuration. - Startup time: More components registered can slow down cold-starts in serverless environments.
- Cognitive load: For small services, flipping through multiple files and abstractions can be cumbersome.
Minimal APIs strip away much of this overhead by allowing you to define endpoints directly in your Program.cs (or any hosting entrypoint), registering only the middleware and services you need.
Getting Started
Create a new project targeting .NET 7 or later:
dotnet new web -o MinimalApiDemo
cd MinimalApiDemoOpen Program.cs. You’ll see something like:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello, world!");
app.Run();
That’s your entire Web API! With just a few lines, you have an HTTP GET endpoint.
Defining CRUD Endpoints
Let’s build a simple “Todo” API. First, define a record for your data:
public record TodoItem(int Id, string Title, bool IsComplete);
Next, in Program.cs, create an in-memory store and map CRUD routes:
var todos = new List<TodoItem> {
new(1, "Learn Minimal APIs", false),
new(2, "Write blog post", false)
};
app.MapGet("/todos", () => Results.Ok(todos))
.WithName("GetAllTodos");
app.MapGet("/todos/{id:int}", (int id) =>
todos.FirstOrDefault(t => t.Id == id)
is TodoItem item
? Results.Ok(item)
: Results.NotFound())
.WithName("GetTodoById");
app.MapPost("/todos", (TodoItem todo) =>
{
todos.Add(todo);
return Results.Created($"/todos/{todo.Id}", todo);
}).WithName("CreateTodo");
app.MapPut("/todos/{id:int}", (int id, TodoItem updated) =>
{
var index = todos.FindIndex(t => t.Id == id);
if (index == -1) return Results.NotFound();
todos[index] = updated;
return Results.NoContent();
}).WithName("UpdateTodo");
app.MapDelete("/todos/{id:int}", (int id) =>
{
var removed = todos.RemoveAll(t => t.Id == id);
return removed > 0 ? Results.NoContent() : Results.NotFound();
}).WithName("DeleteTodo");
Notice how each route is defined inline, with clear return types and status codes.
Dependency Injection and Services
Minimal APIs integrate seamlessly with the built‑in DI container. Suppose you have a ITodoRepository:
public interface ITodoRepository {
IEnumerable<TodoItem> GetAll();
TodoItem? Get(int id);
void Add(TodoItem todo);
bool Update(int id, TodoItem todo);
bool Delete(int id);
}
Register it and use it in your endpoints:
builder.Services.AddSingleton<ITodoRepository, InMemoryTodoRepository>();
app.MapGet("/todos", (ITodoRepository repo) =>
Results.Ok(repo.GetAll()));
By injecting ITodoRepository directly into the lambda, you keep your handlers clean and testable.
Performance Optimization Tips
1.Use Results over ActionResult<T>
The Results static helper avoids the reflection overhead of MVC action results.
2.Enable HTTP/2 and HTTPS
In appsettings.json, ensure Kestrel is configured for HTTP/2 to benefit from multiplexing.
3.AOT & Native Compilation
.NET 8+ supports ahead‑of‑time compilation. In your project file, add:
<PublishAot>true</PublishAot>
This dramatically reduces cold-start times.
4.Source Generators for JSON
System.Text.Json source generators can pre-generate serialization code. Annotate your data types:
[JsonSerializable(typeof(TodoItem))]
public partial class TodoJsonContext : JsonSerializerContext { }
Then configure:
builder.Services.AddControllers().AddJsonOptions(opts =>
opts.JsonSerializerOptions.TypeInfoResolver = TodoJsonContext.Default);
Route Groups and Filters
In .NET 7+, you can group endpoints and apply middleware or filters to the group:
var todoGroup = app.MapGroup("/todos")
.WithTags("Todos")
.RequireAuthorization();
todoGroup.MapGet("/", repo => Results.Ok(repo.GetAll()));
// ...other routes
Observability & Monitoring
Don’t forget telemetry:
- OpenTelemetry:
builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder =>
tracerProviderBuilder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddConsoleExporter());
- Health Checks:
builder.Services.AddHealthChecks()
.AddCheck<DatabaseHealthCheck>("Database");
app.MapHealthChecks("/health");
These small additions can help pinpoint performance issues before they affect users.
Conclusion
Minimal APIs in .NET empower you to build lean, high-performance Web APIs with very little boilerplate. By taking advantage of streamlined routing, built‑in dependency injection, AOT compilation, and source generators, you can achieve both development speed and runtime efficiency. Whether you’re creating a microservice, a serverless function, or a small backend, Minimal APIs are a compelling choice for C# developers aiming to deliver fast, robust APIs with minimal ceremony.