This episode opens up the backend world with ASP.NET Core: Web API basics, controllers, routing, model binding, and response formatting, then the concise Minimal APIs, and an HTTP client with resilience patterns for inter-service communication.

All the previous material leads here: building a Web API. Episode 11 teaches ASP.NET Core — the famously fast .NET web framework — from classic controllers, routing, and model binding, to the Minimal APIs that have become the modern standard.
At the end, you will also use HttpClient to call other APIs with resilience patterns such as retry and timeout. The ability to both call and serve HTTP makes you ready to build distributed systems in phase 5.
Create a project from the webapi template:
dotnet new webapi -n Catalog.Api
cd Catalog.Api
dotnet rundotnet new webapi -n Catalog.Api creates a project with an example weather endpoint. When run, the application uses Kestrel — the .NET web server built directly into the platform. Access http://localhost:5xxx to see the default response.
The modern webapi template uses a Minimal API in Program.cs. This file contains the service registrations and endpoint definitions:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/api/health", () => Results.Ok(new { status = "up" }));
app.Run();app.MapGet("/api/health", ...) registers an HTTP GET endpoint. Results.Ok returns a 200 response with a JSON body. Minimal APIs place endpoints in the same place as configuration, keeping small projects concise.
For large applications structured per feature, use controllers:
[ApiController]
[Route("api/produk")]
public class ProdukController : ControllerBase
{
private readonly AppDbContext _db;
public ProdukController(AppDbContext db)
{
_db = db;
}
[HttpGet("{id:int}")]
public async Task<ActionResult<Produk>> GetById(int id)
{
var produk = await _db.Produk.FindAsync(id);
return produk is null ? NotFound() : Ok(produk);
}
}[ApiController] enables automatic model binding and validation. The api/produk/{id} route maps URLs to methods, and constructor injection (episode 8) connects the controller to the DbContext.
Model binding translates body, query, and route data into method parameters:
public record CreateProdukRequest(string Nama, decimal Harga);
[HttpPost]
public async Task<ActionResult<Produk>> Create(CreateProdukRequest request)
{
var produk = new Produk { Nama = request.Nama, Harga = request.Harga };
_db.Produk.Add(produk);
await _db.SaveChangesAsync();
return CreatedAtAction(nameof(GetById), new { id = produk.Id }, produk);
}The JSON body is automatically parsed into CreateProdukRequest. Responses are converted to JSON with the built-in JsonSerializerOptions — for example, camelCase for consistency with JavaScript consumers.
Minimal APIs excel at small projects and prototypes; controllers for large-scale organization. An example Minimal API that handles POST:
app.MapPost("/api/produk", async (CreateProdukRequest req, AppDbContext db) =>
{
var produk = new Produk { Nama = req.Nama, Harga = req.Harga };
db.Produk.Add(produk);
await db.SaveChangesAsync();
return Results.Created($"/api/produk/{produk.Id}", produk);
});The CreateProdukRequest and AppDbContext parameters are injected automatically by DI. Results.Created returns 201 with a Location header. This approach removes controller boilerplate while the number of endpoints is still small.
To call other APIs, register HttpClient in DI. Use a typed client so the configuration is centralized:
builder.Services.AddHttpClient<ICatalogClient, CatalogClient>(client =>
{
client.BaseAddress = new Uri("https://api.example.com");
client.Timeout = TimeSpan.FromSeconds(10);
});AddHttpClient<T> registers a typed client with configuration and a safe singleton lifetime. BaseAddress and Timeout are set once; connection instantiation is managed automatically by IHttpClientFactory — avoiding the socket exhaustion that often happens when using new HttpClient() repeatedly.
Distributed systems are prone to transient failures. Add retry with Microsoft.Extensions.Http.Resilience:
dotnet add package Microsoft.Extensions.Http.ResilienceThen enable retry at registration:
builder.Services.AddHttpClient<ICatalogClient, CatalogClient>()
.AddStandardResilienceHandler();.AddStandardResilienceHandler() provides retry, timeout, and circuit breaker with sensible default settings. With this, your service stays healthy even when the upstream API is going through a temporary disturbance.
Tip
Avoid creating new HttpClient() per request. Always use IHttpClientFactory or typed clients from DI to avoid connection leaks and socket exhaustion.
[ApiController] for automatic model binding and validation.Results.* provides clear HTTP response helpers.Key takeaways:
In the next episode 12 we will discuss security and identity — ASP.NET Core authentication and authorization, JWT bearer tokens, policy-based authorization, OAuth2 and OpenID Connect integration, and secure headers, CORS, and data protection.