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

2. Basic Program.cs Structure

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

3. MapGet() – Handle GET Requests

app.MapGet("/hello", () => "Hi there!");

4. MapPost() – Handle POST Requests

app.MapPost("/add", (int x, int y) => x + y);

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 });

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);

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.