Introduction
Minimal APIs were introduced in .NET 6 to simplify the creation of small, fast, and lightweight web APIs. They allow developers to write API logic with less code and configuration, without needing controllers or attributes like in traditional ASP.NET MVC.
They are best used when building simple services, microservices, or proof-of-concept applications.
1. Create a New Minimal API Project
To get started
dotnet new web -n MinimalApiDemo
- This template sets up a basic minimal API.
- No controllers or Views folder.
2. Basic Program.cs Structure
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
MapGet
defines a GET endpoint.
- Everything is written in
Program.cs
.
3. MapGet() – Handle GET Requests
app.MapGet("/hello", () => "Hi there!");
- Used to return data or simple messages.
- Can return strings, objects, or JSON.
4. MapPost() – Handle POST Requests
app.MapPost("/add", (int x, int y) => x + y);
- Accepts parameters directly from query or body.
- Ideal for submitting data.
5. MapPut() – Handle PUT Requests
app.MapPut("/update", (int id, string name) => $"Updated {id} to {name}");
6. MapDelete() – Handle DELETE Requests
app.MapDelete("/delete/{id}", (int id) => $"Deleted item {id}");
7. Return JSON Response
app.MapGet("/user", () => new { Name = "John", Age = 25 });
- Automatically serializes object to JSON.
- No need for extra configuration.
8. Reading From Query Strings
app.MapGet("/greet", (string name) => $"Hello, {name}!");
9. Reading From Route Parameters
app.MapGet("/product/{id}", (int id) => $"Product ID: {id}");
10. Read JSON Body in POST
app.MapPost("/person", (Person person) =>
{
return $"Name: {person.Name}, Age: {person.Age}";
});
record Person(string Name, int Age);
- Automatically binds the JSON request body to an object.
- Use
record
or class
.
11. Dependency Injection (DI)
Register service
builder.Services.AddSingleton<MyService>();
Use it in the route
app.MapGet("/info", (MyService svc) => svc.GetInfo());
12. Validation Example
app.MapPost("/validate", (User user) =>
{
if (string.IsNullOrWhiteSpace(user.Email))
return Results.BadRequest("Email is required");
return Results.Ok("Valid User");
});
record User(string Email);
13. Using Results Object
app.MapGet("/status", () => Results.Ok("Working"));
app.MapGet("/fail", () => Results.BadRequest("Something went wrong"));
14. Grouping Routes
var userGroup = app.MapGroup("/users");
userGroup.MapGet("/", () => "All users");
userGroup.MapPost("/", (User u) => $"User {u.Email} added");
15. Middleware Usage
app.Use(async (context, next) =>
{
Console.WriteLine("Request coming in");
await next();
Console.WriteLine("Response going out");
});
16. Enable Swagger (OpenAPI)
Add services
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
Use Swagger UI
app.UseSwagger();
app.UseSwaggerUI();
17. Route Constraints
app.MapGet("/item/{id:int}", (int id) => $"Item ID: {id}");
18. Allow CORS
builder.Services.AddCors();
app.UseCors(policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
19. Environment-based Configuration
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
20. Error Handling
app.UseExceptionHandler("/error");
app.Map("/error", () => Results.Problem("Unexpected error occurred"));
21. Run on Custom URL/Port
Update launchSettings.json
or in code:
builder.WebHost.UseUrls("http://localhost:5001");
22. Route Prefix with [MapGroup] for Versioning
var v1 = app.MapGroup("/api/v1");
v1.MapGet("/products", () => "v1 products");
23. Use Authorization
builder.Services.AddAuthorization();
app.UseAuthorization();
app.MapGet("/secure", () => "Secure Data")
.RequireAuthorization();
24. Async Endpoints
app.MapGet("/delay", async () =>
{
await Task.Delay(1000);
return "Done after delay";
});
25. Binding From Headers / Claims
app.MapGet("/agent", (HttpRequest req) =>
{
var userAgent = req.Headers["User-Agent"];
return $"Your agent: {userAgent}";
});
26. Custom Response Codes
app.MapGet("/notfound", () => Results.StatusCode(404));
27. Conditional Endpoints (MapWhen)
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/admin"), appBuilder =>
{
appBuilder.Run(async ctx => await ctx.Response.WriteAsync("Admin section"));
});
28. Bind Form Data
app.MapPost("/upload", async (HttpRequest req) =>
{
var form = await req.ReadFormAsync();
var file = form.Files["file"];
return $"Uploaded: {file?.FileName}";
});
29. Handle Query, Route, and Body Together
app.MapPost("/mixed/{id}", (int id, string name, MyData data) =>
{
return $"ID: {id}, Name: {name}, Data: {data.Value}";
});
record MyData(string Value);
Comparison: Minimal APIs vs Traditional RESTful APIs (Controllers)
Feature |
Minimal APIs |
RESTful APIs (Controllers) |
Setup |
Very low setup, all in Program.cs |
Needs controllers, attributes, and routing setup |
Performance |
Slightly better (fewer layers) |
Slightly heavier due to abstraction layers |
Best For |
Small services, microservices, and quick APIs |
Large-scale apps with many endpoints |
Testability |
Less structured, harder to unit test |
Easier to test with controllers/services |
Routing |
Code-based (inline) |
Attribute routing or convention-based |
Dependency Injection |
Direct in lambda |
Via constructor injection in controllers |
Maintainability (Large apps) |
Harder to scale/organize |
Better separation of concerns |
Swagger Support |
Fully supported |
Fully supported |
Custom Filters / Middleware |
Limited (no filters, but use middleware) |
Full support (filters, middleware, etc.) |
Validation (FluentValidation, etc.) |
Manual or via packages |
Built-in integration via ModelState |
Extensibility |
Limited to what's in lambdas/middleware |
Highly extensible (Filters, Bindings, etc.) |
When to Use What?
Use Case |
Recommendation |
Quick prototype or POC |
Minimal API |
Microservices / serverless |
Minimal API |
Full-blown business app |
RESTful API (Controllers) |
App needing filters/attributes |
RESTful API |
Highly structured/maintainable code |
RESTful API |
Conclusion
Minimal APIs are a powerful tool for quickly building web APIs with clean and minimal code. They reduce boilerplate and work great for microservices or simple services. But for complex systems, RESTful APIs using controllers still offer more structure and long-term scalability.
Use Minimal APIs when you want speed and simplicity, but switch to RESTful APIs when you need organization, structure, testability, or layered architecture.