iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

ASP.NET Minimal API

ASP.NET Core minimal APIs give you a tiny, typed web stack: routing, model binding, validation, DI, auth, OpenAPI — all from `var app = WebApplication.CreateBuilder(args).Build();`. Pick it for new services in 2026; controllers are still fine but minimal APIs hit the sweet spot for size + clarity.

A minimal API with JWT auth, validation, and OpenAPI

EXAMPLE
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Mvc;
using FluentValidation;
using FluentValidation.AspNetCore;
using System.Text;
using System.Security.Cryptography;
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;

var builder = WebApplication.CreateBuilder(args);

// 1) Options + DI
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<IOrderRepository, InMemoryOrderRepository>();
builder.Services.AddValidatorsFromAssemblyContaining<CreateOrderValidator>();
builder.Services.AddFluentValidationAutoValidation();

// 2) Auth — JWT bearer
var key = new SymmetricSecurityKey(Convert.FromBase64String(builder.Configuration["Auth:SigningKey"]!));
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(o =>
    {
        o.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "https://api.example.com",
            ValidAudience = "shop-api",
            IssuerSigningKey = key,
        };
    });
builder.Services.AddAuthorization();

// 3) Build
var app = builder.Build();

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); }

// 4) Endpoints — typed parameters, model binding, validation
app.MapGet("/health", () => Results.Ok(new { status = "ok" }));

app.MapPost("/orders",
    async ([FromBody] CreateOrder body, IOrderRepository repo, HttpContext ctx) =>
    {
        var uid = ctx.User.FindFirstValue(ClaimTypes.NameIdentifier);
        var id  = await repo.CreateAsync(uid!, body.Customer, body.TotalCents, CancellationToken.None);
        return Results.Created($"/orders/{id}", new { id });
    })
    .RequireAuthorization()
    .WithName("CreateOrder")
    .WithOpenApi();

app.MapGet("/orders/{id}",
    async (string id, IOrderRepository repo) =>
    {
        var o = await repo.FindAsync(id);
        return o is null ? Results.NotFound() : Results.Ok(o);
    })
    .RequireAuthorization()
    .WithName("GetOrder")
    .WithOpenApi();

// 5) Issue a JWT — for demo only; in real life the auth server does this
app.MapPost("/auth/token", (LoginInput login) =>
{
    if (login.Email != "alice@example.com" || login.Password != "hunter2")
        return Results.Unauthorized();

    var handler = new JwtSecurityTokenHandler();
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    var token = handler.CreateEncodedJwt(
        issuer: "https://api.example.com", audience: "shop-api",
        subject: new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "u1") }),
        notBefore: DateTime.UtcNow, expires: DateTime.UtcNow.AddMinutes(15),
        issuedAt: DateTime.UtcNow, signingCredentials: creds);
    return Results.Ok(new { accessToken = token });
});

app.Run();

// ============================================================
// Records + validators
// ============================================================
public record CreateOrder(string Customer, long TotalCents);
public record Order(string Id, string Customer, long TotalCents, string Status);
public record LoginInput(string Email, string Password);

public class CreateOrderValidator : AbstractValidator<CreateOrder>
{
    public CreateOrderValidator()
    {
        RuleFor(x => x.Customer).NotEmpty().MaximumLength(120);
        RuleFor(x => x.TotalCents).GreaterThanOrEqualTo(0);
    }
}

public interface IOrderRepository
{
    Task<string> CreateAsync(string uid, string customer, long totalCents, CancellationToken ct);
    Task<Order?> FindAsync(string id);
}

public class InMemoryOrderRepository : IOrderRepository
{
    private readonly Dictionary<string, Order> _store = new();
    public Task<string> CreateAsync(string uid, string customer, long totalCents, CancellationToken ct)
    {
        var id = Guid.NewGuid().ToString();
        _store[id] = new Order(id, customer, totalCents, "new");
        return Task.FromResult(id);
    }
    public Task<Order?> FindAsync(string id) => Task.FromResult(_store.TryGetValue(id, out var o) ? o : null);
}

Why it matters

Minimal APIs + FluentValidation + JWT bearer cover ~80% of new service endpoints with less than half the code of the controller-based equivalent. Reach for controllers when you need attribute-style filters across many endpoints; otherwise the minimal API surface is the right default for new ASP.NET Core services.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
var app = WebApplication.CreateBuilder(args).Build();
app.MapGet("/hi", () => "Hello");
app.Run();
Try it Yourself »

Discussion

Loading…