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…