ASP.NET Core Minimal APIs 2026: Arsitektur, Performa dan Pertanyaan Interview

Panduan lengkap ASP.NET Core Minimal APIs dengan arsitektur modern, optimasi performa Native AOT, dan pertanyaan interview umum untuk developer .NET.

ASP.NET Core Minimal APIs 2026: Arsitektur, Performa dan Pertanyaan Interview

ASP.NET Core Minimal APIs menghilangkan boilerplate yang berlebihan dari controller MVC tradisional, menawarkan pendekatan yang lebih ringkas untuk membangun HTTP endpoint. Diperkenalkan di .NET 6 dan disempurnakan melalui .NET 8, 9, hingga .NET 10, Minimal APIs telah matang menjadi pilihan production-ready untuk microservices, serverless functions, dan lightweight web services.

Minimal APIs vs Controllers

Minimal APIs menggunakan top-level route handlers yang didefinisikan langsung di Program.cs, sedangkan MVC controllers memerlukan definisi class, attributes, dan convention-based routing. Untuk operasi CRUD sederhana atau microservices dengan kurang dari 20 endpoint, Minimal APIs biasanya mengurangi kode sebesar 40-60%.

Arsitektur Minimal API dan Request Pipeline

Request pipeline ASP.NET Core memproses HTTP requests melalui komponen middleware sebelum mencapai endpoint handlers. Minimal APIs terintegrasi dengan baik dalam pipeline ini sambil menawarkan sintaks yang lebih deklaratif untuk definisi route.

Program.cscsharp
var builder = WebApplication.CreateBuilder(args);

// Register services for dependency injection
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddScoped<IProductRepository, ProductRepository>();

var app = builder.Build();

// Middleware pipeline configuration
app.UseExceptionHandler("/error");
app.UseHttpsRedirection();
app.UseAuthorization();

// Minimal API endpoint definitions
app.MapGet("/products", async (IProductRepository repo) =>
    Results.Ok(await repo.GetAllAsync()));

app.MapGet("/products/{id:int}", async (int id, IProductRepository repo) =>
    await repo.GetByIdAsync(id) is Product product
        ? Results.Ok(product)
        : Results.NotFound());

app.Run();

Pola ini memusatkan definisi route sambil mempertahankan akses penuh ke dependency injection container. Parameter IProductRepository mendemonstrasikan constructor-less injection langsung ke dalam handler delegates.

Route Groups dan Organisasi Endpoint

Seiring pertumbuhan aplikasi, pengorganisasian endpoint menjadi sangat penting. Route groups, diperkenalkan di .NET 7, menyediakan namespacing dan konfigurasi bersama tanpa mengorbankan pendekatan minimal.

ProductEndpoints.cscsharp
public static class ProductEndpoints
{
    public static void MapProductEndpoints(this WebApplication app)
    {
        var group = app.MapGroup("/api/products")
            .WithTags("Products")
            .RequireAuthorization();

        group.MapGet("/", GetAllProducts);
        group.MapGet("/{id:int}", GetProductById);
        group.MapPost("/", CreateProduct)
            .Accepts<CreateProductRequest>("application/json")
            .Produces<Product>(StatusCodes.Status201Created);
        group.MapPut("/{id:int}", UpdateProduct);
        group.MapDelete("/{id:int}", DeleteProduct)
            .RequireAuthorization("AdminOnly");
    }

    private static async Task<IResult> GetAllProducts(
        IProductRepository repo,
        CancellationToken ct)
    {
        var products = await repo.GetAllAsync(ct);
        return Results.Ok(products);
    }

    private static async Task<IResult> GetProductById(
        int id,
        IProductRepository repo,
        CancellationToken ct)
    {
        var product = await repo.GetByIdAsync(id, ct);
        return product is not null
            ? Results.Ok(product)
            : Results.NotFound();
    }

    private static async Task<IResult> CreateProduct(
        CreateProductRequest request,
        IProductRepository repo,
        IValidator<CreateProductRequest> validator,
        CancellationToken ct)
    {
        var validation = await validator.ValidateAsync(request, ct);
        if (!validation.IsValid)
            return Results.ValidationProblem(validation.ToDictionary());

        var product = await repo.CreateAsync(request.ToProduct(), ct);
        return Results.Created($"/api/products/{product.Id}", product);
    }
}

Memanggil app.MapProductEndpoints() di Program.cs mendaftarkan semua product routes dengan shared authorization requirements dan OpenAPI metadata. Struktur ini berskala baik untuk aplikasi dengan ratusan endpoint.

Parameter Binding dan Validasi

Minimal APIs mendukung berbagai binding sources: route parameters, query strings, headers, request bodies, dan services dari DI. Memahami binding precedence mencegah kesalahan umum dalam interview.

Program.cs - Parameter binding examplescsharp
app.MapGet("/search", (
    [FromQuery] string? query,           // Explicit query string
    [FromQuery] int page = 1,            // Default value
    [FromQuery] int pageSize = 20,       // Default value
    [FromHeader(Name = "X-Correlation-Id")] string? correlationId,
    ILogger<Program> logger) =>
{
    logger.LogInformation("Search request: {Query}, Page: {Page}, CorrelationId: {CorrelationId}",
        query, page, correlationId);

    return Results.Ok(new { query, page, pageSize, correlationId });
});

// Complex model binding with validation
app.MapPost("/orders", async (
    [FromBody] CreateOrderRequest request,
    [FromServices] IValidator<CreateOrderRequest> validator,
    [FromServices] IOrderService orderService,
    HttpContext context,
    CancellationToken ct) =>
{
    var validationResult = await validator.ValidateAsync(request, ct);

    if (!validationResult.IsValid)
    {
        return Results.ValidationProblem(
            validationResult.Errors
                .GroupBy(e => e.PropertyName)
                .ToDictionary(
                    g => g.Key,
                    g => g.Select(e => e.ErrorMessage).ToArray()));
    }

    var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
    var order = await orderService.CreateOrderAsync(request, userId!, ct);

    return Results.Created($"/orders/{order.Id}", order);
});

Attribute [FromBody] bersifat opsional untuk complex types namun meningkatkan keterbacaan. FluentValidation terintegrasi secara natural melalui dependency injection, memisahkan logika validasi dari endpoint handlers.

Prioritas Binding Source

Ketika tidak ada attribute yang ditentukan, Minimal APIs menyimpulkan binding sources: route parameters terlebih dahulu, kemudian query strings untuk simple types, dan request body untuk complex types. Explicit attributes seperti [FromQuery] atau [FromBody] mengganti perilaku ini.

Optimasi Performa dengan Native AOT

.NET 8 memperkenalkan dukungan Native AOT (Ahead-of-Time) compilation untuk Minimal APIs, menghasilkan executable mandiri dengan waktu startup sub-milidetik. Kemampuan ini menjadikan Minimal APIs ideal untuk deployment serverless di mana cold start latency sangat penting.

Program.cs - AOT-compatible configurationcsharp
var builder = WebApplication.CreateSlimBuilder(args);

// AOT-friendly JSON serialization
builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});

var app = builder.Build();

app.MapGet("/health", () => Results.Ok(new HealthResponse("Healthy", DateTime.UtcNow)));

app.Run();

// Source-generated JSON serializer context
[JsonSerializable(typeof(HealthResponse))]
[JsonSerializable(typeof(Product))]
[JsonSerializable(typeof(List<Product>))]
internal partial class AppJsonContext : JsonSerializerContext { }

public record HealthResponse(string Status, DateTime CheckedAt);

Method CreateSlimBuilder mengecualikan fitur framework yang tidak diperlukan, sementara source-generated JsonSerializerContext mengeliminasi runtime reflection untuk JSON serialization. Published AOT binaries untuk simple APIs biasanya berukuran 10-15 MB dibandingkan 80+ MB untuk standard self-contained deployments.

Typed Results dan Response Metadata

.NET 7 memperkenalkan TypedResults untuk verifikasi compile-time dari response types, meningkatkan akurasi dokumentasi OpenAPI dan mendeteksi type mismatches selama development.

csharp
// Strongly-typed results with OpenAPI metadata
app.MapGet("/products/{id:int}", async Task<Results<Ok<Product>, NotFound, ProblemHttpResult>> (
    int id,
    IProductRepository repo,
    CancellationToken ct) =>
{
    try
    {
        var product = await repo.GetByIdAsync(id, ct);

        return product is not null
            ? TypedResults.Ok(product)
            : TypedResults.NotFound();
    }
    catch (Exception ex)
    {
        return TypedResults.Problem(
            detail: "An error occurred retrieving the product",
            statusCode: StatusCodes.Status500InternalServerError);
    }
})
.WithName("GetProductById")
.WithOpenApi(operation =>
{
    operation.Summary = "Retrieves a product by ID";
    operation.Description = "Returns the product details or 404 if not found";
    return operation;
});

Union type Results<T1, T2, T3> mendeklarasikan semua kemungkinan response types, yang digunakan Swagger/OpenAPI generators untuk menghasilkan dokumentasi yang akurat. Pola ini sangat berharga ketika mempersiapkan pertanyaan interview tentang API design.

Siap menguasai wawancara .NET Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Endpoint Filters untuk Cross-Cutting Concerns

Endpoint filters menyediakan fungsionalitas mirip middleware yang dibatasi pada endpoint atau groups tertentu, menangani concerns seperti logging, caching, dan request transformation.

ValidationFilter.cscsharp
public class ValidationFilter<T> : IEndpointFilter where T : class
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        var validator = context.HttpContext
            .RequestServices
            .GetService<IValidator<T>>();

        if (validator is null)
            return await next(context);

        var argument = context.Arguments
            .OfType<T>()
            .FirstOrDefault();

        if (argument is null)
            return await next(context);

        var validationResult = await validator.ValidateAsync(argument);

        if (!validationResult.IsValid)
        {
            return Results.ValidationProblem(
                validationResult.Errors
                    .GroupBy(e => e.PropertyName)
                    .ToDictionary(
                        g => g.Key,
                        g => g.Select(e => e.ErrorMessage).ToArray()));
        }

        return await next(context);
    }
}

// Usage in Program.cs
app.MapPost("/products", CreateProduct)
    .AddEndpointFilter<ValidationFilter<CreateProductRequest>>();

// Global filter registration via route group
var api = app.MapGroup("/api")
    .AddEndpointFilter(async (context, next) =>
    {
        var logger = context.HttpContext
            .RequestServices
            .GetRequiredService<ILogger<Program>>();

        var stopwatch = Stopwatch.StartNew();
        var result = await next(context);
        stopwatch.Stop();

        logger.LogInformation(
            "Endpoint {Method} {Path} completed in {ElapsedMs}ms",
            context.HttpContext.Request.Method,
            context.HttpContext.Request.Path,
            stopwatch.ElapsedMilliseconds);

        return result;
    });

Filters dieksekusi sesuai urutan registrasi, dengan innermost filter paling dekat dengan endpoint handler. Arsitektur ini memungkinkan pemisahan yang bersih dari logika validasi, logging, dan authorization.

Pola Authentication dan Authorization

Minimal APIs mendukung mekanisme authentication dan authorization yang sama dengan MVC controllers, dengan sintaks konfigurasi yang lebih deklaratif.

Program.cs - JWT authentication setupcsharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
        };
    });

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("AdminOnly", policy =>
        policy.RequireRole("Admin"))
    .AddPolicy("PremiumUser", policy =>
        policy.RequireClaim("subscription", "premium", "enterprise"));

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

// Protected endpoints
app.MapGet("/admin/users", async (IUserService userService) =>
    Results.Ok(await userService.GetAllUsersAsync()))
    .RequireAuthorization("AdminOnly");

app.MapGet("/profile", async (ClaimsPrincipal user, IUserService userService) =>
{
    var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
    var profile = await userService.GetProfileAsync(userId!);
    return Results.Ok(profile);
})
.RequireAuthorization();

// Anonymous endpoint within protected group
var protectedGroup = app.MapGroup("/api/secure")
    .RequireAuthorization();

protectedGroup.MapGet("/public-info", () => Results.Ok("This is public"))
    .AllowAnonymous();

Extension method RequireAuthorization menerima nama policy atau dapat dipanggil tanpa argumen untuk memerlukan user terauthentikasi. Memahami pola-pola ini sangat penting untuk pertanyaan interview tentang authentication dan authorization.

Pertanyaan Interview: Pola Umum

Interview teknis sering mengeksplorasi perbedaan antara Minimal APIs dan traditional controllers. Tabel berikut merangkum perbedaan utama:

| Aspek | Minimal APIs | MVC Controllers | |-------|--------------|----------------| | Boilerplate | Rendah - handler delegates langsung | Lebih tinggi - class + method + attributes | | Routing | Inline dengan MapGet, MapPost | Attribute atau convention-based | | Model Binding | Otomatis dengan optional attributes | Convention + attributes | | Filters | Endpoint filters | Action filters + middleware | | Dukungan AOT | Penuh sejak .NET 8 | Terbatas, reflection-heavy | | Testability | Function-based, mudah di-unit test | Memerlukan instantiasi controller | | Cocok Untuk | Microservices, simple APIs | Aplikasi besar, complex workflows |

Tips Interview

Ketika ditanya "Kapan memilih controllers dibanding Minimal APIs?", sebutkan aplikasi kompleks yang memerlukan action filters, kustomisasi model binding, atau codebase yang sudah ada dengan pola MVC yang established. Minimal APIs unggul untuk greenfield microservices dan serverless functions.

Testing Endpoint Minimal API

Integration testing dengan WebApplicationFactory menyediakan verifikasi endpoint yang realistis tanpa men-deploy aplikasi.

ProductEndpointsTests.cscsharp
public class ProductEndpointsTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    private readonly WebApplicationFactory<Program> _factory;

    public ProductEndpointsTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureServices(services =>
            {
                // Replace real repository with mock
                services.RemoveAll<IProductRepository>();
                services.AddScoped<IProductRepository, MockProductRepository>();
            });
        });
        _client = _factory.CreateClient();
    }

    [Fact]
    public async Task GetProducts_ReturnsOkWithProductList()
    {
        // Act
        var response = await _client.GetAsync("/api/products");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);

        var products = await response.Content
            .ReadFromJsonAsync<List<Product>>();

        products.Should().NotBeNull();
        products.Should().HaveCountGreaterThan(0);
    }

    [Fact]
    public async Task GetProductById_WithInvalidId_ReturnsNotFound()
    {
        // Act
        var response = await _client.GetAsync("/api/products/99999");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    [Fact]
    public async Task CreateProduct_WithValidRequest_ReturnsCreated()
    {
        // Arrange
        var request = new CreateProductRequest("Test Product", 29.99m, "Test Description");

        // Act
        var response = await _client.PostAsJsonAsync("/api/products", request);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Created);
        response.Headers.Location.Should().NotBeNull();
    }
}

Untuk eksplorasi lebih mendalam tentang clean architecture patterns dalam aplikasi .NET, service layer sebaiknya diuji secara independen dengan unit tests, sementara integration tests memverifikasi full request pipeline.

Kesimpulan

Minimal APIs merepresentasikan pendekatan modern untuk membangun HTTP services di ASP.NET Core:

  • Route groups dan organisasi endpoint berskala dari microservices sederhana hingga aplikasi kompleks
  • Endpoint filters menyediakan pemisahan yang bersih dari cross-cutting concerns seperti validasi dan logging
  • TypedResults memungkinkan verifikasi compile-time response type dan dokumentasi OpenAPI yang akurat
  • Native AOT compilation menghadirkan waktu startup sub-milidetik untuk serverless deployments
  • Integration testing dengan WebApplicationFactory memastikan verifikasi endpoint yang realistis

Untuk persiapan interview, fokus pada mengartikulasikan kapan Minimal APIs tepat dibandingkan traditional controllers, dan demonstrasikan pemahaman tentang request pipeline, dependency injection, dan authentication patterns.

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Bagikan

Artikel terkait