Section 5 of 7
Minimal APIs
🎯 What You'll Learn
- What Minimal APIs are
- MapGet, MapPost, MapPut, MapDelete
- Route groups
- Filters and middleware
- When to use Minimal APIs vs Controllers
- Complete CRUD example
What are Minimal APIs?
Minimal APIs are a simplified way to build HTTP APIs with minimal code and configuration. Introduced in .NET 6, they reduce ceremony and boilerplate.
Controllers vs Minimal APIs
| Feature | Controllers | Minimal APIs |
|---|---|---|
| Code | More boilerplate | Less code |
| Organization | Class-based | Function-based |
| Features | Full-featured | Lightweight |
| Best For | Large, complex APIs | Simple, microservices |
Basic Minimal API
Program.cs
C#
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// GET /hello
app.MapGet("/hello", () => "Hello World!");
// GET /products
app.MapGet("/products", () => new[] { "Laptop", "Mouse", "Keyboard" });
app.Run();
HTTP Methods
MapGet, MapPost, MapPut, MapDelete
C#
// GET /api/products
app.MapGet("/api/products", () => "Get all products");
// GET /api/products/123
app.MapGet("/api/products/{id}", (int id) => $"Product {id}");
// POST /api/products
app.MapPost("/api/products", (Product product) => "Product created");
// PUT /api/products/123
app.MapPut("/api/products/{id}", (int id, Product product) => "Product updated");
// DELETE /api/products/123
app.MapDelete("/api/products/{id}", (int id) => "Product deleted");
Returning Results
Results Helpers
C#
// 200 OK
app.MapGet("/products", () => Results.Ok(new[] { "Laptop" }));
// 201 Created
app.MapPost("/products", (Product p) =>
Results.Created($"/products/{p.Id}", p));
// 204 No Content
app.MapDelete("/products/{id}", (int id) => Results.NoContent());
// 404 Not Found
app.MapGet("/products/{id}", (int id) =>
id > 0 ? Results.Ok(new Product()) : Results.NotFound());
// 400 Bad Request
app.MapPost("/products", (Product p) =>
string.IsNullOrEmpty(p.Name)
? Results.BadRequest("Name is required")
: Results.Ok(p));
Dependency Injection
Injecting Services
C#
// Register service
builder.Services.AddScoped<IProductService, ProductService>();
var app = builder.Build();
// Inject service into endpoint
app.MapGet("/products", async (IProductService service) =>
{
var products = await service.GetAllAsync();
return Results.Ok(products);
});
Route Groups
Route groups organize related endpoints and apply common configuration.
Using Route Groups
C#
var products = app.MapGroup("/api/products");
products.MapGet("/", async (IProductService service) =>
await service.GetAllAsync());
products.MapGet("/{id}", async (int id, IProductService service) =>
{
var product = await service.GetByIdAsync(id);
return product == null ? Results.NotFound() : Results.Ok(product);
});
products.MapPost("/", async (CreateProductDto dto, IProductService service) =>
{
var product = await service.CreateAsync(dto);
return Results.Created($"/api/products/{product.Id}", product);
});
products.MapPut("/{id}", async (int id, UpdateProductDto dto, IProductService service) =>
{
await service.UpdateAsync(id, dto);
return Results.NoContent();
});
products.MapDelete("/{id}", async (int id, IProductService service) =>
{
await service.DeleteAsync(id);
return Results.NoContent();
});
Group Configuration
Apply Metadata to Group
C#
var products = app.MapGroup("/api/products")
.RequireAuthorization() // All endpoints require auth
.WithTags("Products") // OpenAPI tag
.WithOpenApi(); // Generate OpenAPI docs
Filters
Endpoint Filters
C#
// Validation filter
app.MapPost("/products", (CreateProductDto dto) => Results.Ok(dto))
.AddEndpointFilter(async (context, next) =>
{
var dto = context.GetArgument<CreateProductDto>(0);
if (string.IsNullOrEmpty(dto.Name))
return Results.BadRequest("Name is required");
return await next(context);
});
Complete InvenTrack Example
Program.cs
C#
var builder = WebApplication.CreateBuilder(args);
// Register services
builder.Services.AddScoped<IProductService, ProductService>();
var app = builder.Build();
// Products API group
var products = app.MapGroup("/api/products")
.WithTags("Products");
// GET /api/products
products.MapGet("/", async (IProductService service) =>
{
var allProducts = await service.GetAllAsync();
return Results.Ok(allProducts);
})
.WithName("GetProducts")
.Produces<List<Product>>(200);
// GET /api/products/{id}
products.MapGet("/{id}", async (int id, IProductService service) =>
{
var product = await service.GetByIdAsync(id);
return product == null
? Results.NotFound()
: Results.Ok(product);
})
.WithName("GetProduct")
.Produces<Product>(200)
.Produces(404);
// POST /api/products
products.MapPost("/", async (CreateProductDto dto, IProductService service) =>
{
var product = await service.CreateAsync(dto);
return Results.CreatedAtRoute("GetProduct", new { id = product.Id }, product);
})
.Produces<Product>(201)
.Produces(400);
// PUT /api/products/{id}
products.MapPut("/{id}", async (int id, UpdateProductDto dto, IProductService service) =>
{
var exists = await service.ExistsAsync(id);
if (!exists)
return Results.NotFound();
await service.UpdateAsync(id, dto);
return Results.NoContent();
})
.Produces(204)
.Produces(404);
// DELETE /api/products/{id}
products.MapDelete("/{id}", async (int id, IProductService service) =>
{
var exists = await service.ExistsAsync(id);
if (!exists)
return Results.NotFound();
await service.DeleteAsync(id);
return Results.NoContent();
})
.Produces(204)
.Produces(404);
app.Run();
When to Use Minimal APIs
| Use Minimal APIs | Use Controllers |
|---|---|
| Simple APIs | Complex APIs |
| Microservices | Large monoliths |
| Prototypes | Enterprise applications |
| Few endpoints | Many endpoints |
| Performance-critical | Feature-rich requirements |
Best Practices
- Route groups: Organize related endpoints
- Dependency injection: Inject services into endpoints
- Results helpers: Use Results.Ok(), Results.NotFound(), etc.
- Metadata: Use WithName(), WithTags(), Produces()
- Async: Use async/await for I/O operations
- Validation: Add endpoint filters for validation
- Organization: Consider extension methods for large APIs
Key Takeaways
- Minimal APIs: Lightweight alternative to controllers
- MapGet/Post/Put/Delete: Define HTTP endpoints
- Results helpers: Return typed responses
- Route groups: Organize and configure endpoints
- Filters: Add cross-cutting concerns
- DI: Inject services into endpoint handlers
- Less code: Reduced boilerplate
- Performance: Faster startup and execution
🎯 Next Steps
You now understand Minimal APIs! In the next section, we'll explore DTOs and AutoMapper—how to separate your API models from domain models and automatically map between them.