.NET Minimal APIs: Hafif ve Hızlı Endpoint'ler

13 dakika okuma9 Şubat 2026Güncellendi: 9 Mar 2026
.NET Minimal APIMinimal APIs tutorial.NET 8 minimal apiController vs Minimal APILightweight .NET APIFast .NET endpoints.NET microservicesServerless .NET

# .NET Minimal APIs: Hafif ve Hizli Endpoint'ler

Minimal APIs, .NET 6 ile birlikte ASP.NET Core'a eklenen, HTTP endpoint'lerini tanımlamanın kökten sadeleştirilmiş yoludur. Controller sınıfları, attribute'lar ve katmanlı dosya yapıları yerine, route'larınızı doğrudan lambda ifadeleriyle tanımlarsınız. Ama bu sadeliğin altında ASP.NET Core platformunun tüm gücü durur: dependency injection, middleware, authentication, authorization ve OpenAPI desteği eksiksiz çalışır.

Minimal API'lerle kurduğum mikroservislerde başlangıç süresi gözle görülür biçimde düştü ve ekibe yeni katılan geliştiricilerin adaptasyon süreci belirgin şekilde kısaldı. Bir geliştirici `Program.cs` dosyasını açıp servisteki tüm route'ları görebiliyor ve dakikalar içinde katkıda bulunmaya başlayabiliyor.

Minimal API'ler Nerede Parlıyor?

  • **Odaklı mikroservisler**: 5-20 endpoint'lik sınırlı yüzey alanına sahip servisler
  • **Dahili araçlar ve BFF servisleri**: Frontend'ler için veri toplayan ara katmanlar
  • **MVP'ler ve prototipler**: Scaffolding yükü olmadan hızlı iterasyon gerektiren projeler
  • **Basit CRUD ve webhook handler'ları**: Controller seremonisinin katma değer sağlamadığı durumlar
  • **Serverless fonksiyonlar**: Soğuk başlatma süresinin kritik olduğu container tabanlı iş yükleri
  • **Event-driven servisler**: Mesaj alan ve sağlık/durum endpoint'leri sunan servisler
  • Temeller: Hızlı Başlangıç

    Eksiksiz bir Minimal API tek dosyada yaşayabilir:

    csharp
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddSingleton<IProductRepository, ProductRepository>();
    
    var app = builder.Build();
    
    app.MapGet(class="code-string">"/products", async (IProductRepository repo) =>
    {
        var products = await repo.GetAllAsync();
        return Results.Ok(products);
    });
    
    app.MapGet(class="code-string">"/products/{id:int}", async (int id, IProductRepository repo) =>
    {
        var product = await repo.GetByIdAsync(id);
        return product is not null ? Results.Ok(product) : Results.NotFound();
    });
    
    app.MapPost(class="code-string">"/products", async (CreateProductRequest request, IProductRepository repo) =>
    {
        var product = await repo.CreateAsync(request);
        return Results.Created($class="code-string">"/products/{product.Id}", product);
    });
    
    app.Run();

    Dependency injection'ın parametre binding üzerinden nasıl çalıştığına dikkat edin. Framework, handler parametrelerini inceleyerek servisleri, route değerlerini, query string'leri ve body içeriğini otomatik olarak çözümler.

    Parametre Binding Detayları

    Minimal API'ler, hem güçlü hem de sezgisel olan convention tabanlı bir binding sistemi kullanır:

    csharp
    class=class="code-string">"code-comment">// Route parametreleri
    app.MapGet(class="code-string">"/users/{id:guid}", (Guid id) => ...);
    
    class=class="code-string">"code-comment">// Query string parametreleri
    app.MapGet(class="code-string">"/search", (string? query, int page = class="code-number">1, int pageSize = class="code-number">20) => ...);
    
    class=class="code-string">"code-comment">// Header binding
    app.MapGet(class="code-string">"/protected", ([FromHeader(Name = class="code-string">"X-Api-Key")] string apiKey) => ...);
    
    class=class="code-string">"code-comment">// Body binding (POST/PUTclass="code-string">'ta karmaşık tipler için otomatik)
    app.MapPost(class="code-string">"/orders", (CreateOrderRequest request) => ...);
    
    class=class="code-string">"code-comment">// Servis injection
    app.MapGet(class="code-string">"/stats", (IAnalyticsService analytics, ILogger<Program> logger) => ...);
    
    class=class="code-string">"code-comment">// Tam kontrol gerektiğinde HttpContext erişimi
    app.MapGet(class="code-string">"/custom", (HttpContext context) => ...);
    
    class=class="code-string">"code-comment">// Birden fazla binding'i gruplamak için AsParameters
    app.MapGet(class="code-string">"/products", async ([AsParameters] ProductFilterRequest filter) => ...);

    `[AsParameters]` attribute'u, çok sayıda query parametresi olan GET endpoint'lerinde özellikle kullanışlıdır:

    csharp
    public record ProductFilterRequest(
        string? Category,
        decimal? MinPrice,
        decimal? MaxPrice,
        int Page = class="code-number">1,
        int PageSize = class="code-number">20,
        [FromServices] IProductRepository Repository = default!
    );

    Typed Results ile Net Kontratlar

    .NET 7'den itibaren gelen `TypedResults`, endpoint kontratlarınızı açık hale getirir ve doğrudan OpenAPI dokümantasyonunu besler:

    csharp
    app.MapGet(class="code-string">"/orders/{id}", async Task<Results<Ok<Order>, NotFound>> (int id, IOrderService service) =>
    {
        var order = await service.GetByIdAsync(id);
        return order is not null
            ? TypedResults.Ok(order)
            : TypedResults.NotFound();
    });
    
    app.MapPost(class="code-string">"/orders", async Task<Results<Created<Order>, ValidationProblem>> (
        CreateOrderRequest request, IOrderService service, IValidator<CreateOrderRequest> validator) =>
    {
        var validation = await validator.ValidateAsync(request);
        if (!validation.IsValid)
            return TypedResults.ValidationProblem(validation.ToDictionary());
    
        var order = await service.CreateAsync(request);
        return TypedResults.Created($class="code-string">"/orders/{order.Id}", order);
    });

    `Results` birleşim tipi, hem derleyiciye hem de OpenAPI üretecine bir endpoint'in tam olarak hangi yanıtları üreteceğini söyler.

    Route Groups ile Feature Bazlı Organizasyon

    Route group'lar, ilişkili endpoint'ler arasında yapılandırmayı paylaşmanızı sağlayarak tekrarı azaltır:

    csharp
    var api = app.MapGroup(class="code-string">"/api/v1");
    
    var products = api.MapGroup(class="code-string">"/products")
        .WithTags(class="code-string">"Products")
        .RequireAuthorization(class="code-string">"ApiScope");
    
    products.MapGet(class="code-string">"/", GetAllProducts);
    products.MapGet(class="code-string">"/{id:int}", GetProductById);
    products.MapPost(class="code-string">"/", CreateProduct).RequireAuthorization(class="code-string">"Admin");
    products.MapPut(class="code-string">"/{id:int}", UpdateProduct).RequireAuthorization(class="code-string">"Admin");
    products.MapDelete(class="code-string">"/{id:int}", DeleteProduct).RequireAuthorization(class="code-string">"Admin");
    
    var orders = api.MapGroup(class="code-string">"/orders")
        .WithTags(class="code-string">"Orders")
        .RequireAuthorization();
    
    orders.MapGet(class="code-string">"/", GetUserOrders);
    orders.MapGet(class="code-string">"/{id:int}", GetOrderById);
    orders.MapPost(class="code-string">"/", PlaceOrder);

    Group'lar iç içe geçmeyi, paylaşımlı filtreleri ve yetkilendirme politikalarını destekler. Basit örneklerin ötesinde Minimal API'leri organize etmenin temel yapı taşıdır.

    Endpoint Filters: Kesişen İlgiler

    Endpoint filter'lar, MVC'deki action filter'ların Minimal API karşılığıdır. İstekleri handler çalışmadan önce ve sonra yakalar:

    csharp
    public class ValidationFilter<T> : IEndpointFilter where T : class
    {
        public async ValueTask<object?> InvokeAsync(
            EndpointFilterInvocationContext context,
            EndpointFilterDelegate next)
        {
            var request = context.Arguments.OfType<T>().FirstOrDefault();
            if (request is null)
                return TypedResults.BadRequest(class="code-string">"Request body gereklidir.");
    
            var validator = context.HttpContext.RequestServices.GetService<IValidator<T>>();
            if (validator is not null)
            {
                var result = await validator.ValidateAsync(request);
                if (!result.IsValid)
                    return TypedResults.ValidationProblem(result.ToDictionary());
            }
    
            return await next(context);
        }
    }
    
    class=class="code-string">"code-comment">// Kullanım
    products.MapPost(class="code-string">"/", CreateProduct)
        .AddEndpointFilter<ValidationFilter<CreateProductRequest>>();

    Daha basit senaryolar için inline filter da tanımlayabilirsiniz:

    csharp
    app.MapGet(class="code-string">"/items/{id:int}", (int id) => ...)
        .AddEndpointFilter(async (context, next) =>
        {
            var id = context.GetArgument<int>(class="code-number">0);
            if (id <= class="code-number">0)
                return TypedResults.BadRequest(class="code-string">"ID pozitif olmalıdır.");
            return await next(context);
        });

    OpenAPI Entegrasyonu

    .NET 9'dan itibaren Minimal API'ler, framework'e gömülü birinci sınıf OpenAPI desteğine sahiptir:

    csharp
    builder.Services.AddOpenApi();
    
    var app = builder.Build();
    
    app.MapOpenApi();
    
    app.MapGet(class="code-string">"/products/{id}", async (int id, IProductRepository repo) =>
    {
        var product = await repo.GetByIdAsync(id);
        return product is not null ? Results.Ok(product) : Results.NotFound();
    })
    .WithName(class="code-string">"GetProductById")
    .WithDescription(class="code-string">"Benzersiz tanımlayıcısıyla tek bir ürünü getirir.")
    .Produces<Product>(class="code-number">200)
    .Produces(class="code-number">404)
    .WithTags(class="code-string">"Products");

    Daha eski .NET sürümlerinde Swashbuckle veya NSwag sorunsuz entegre olur. Asıl önemli nokta, `TypedResults`'ın otomatik olarak doğru şemalar üretmesidir; bu sayede manuel annotation olmadan güvenilir dokümantasyon elde edersiniz.

    Minimal API'leri Ölçekte Yapılandırma

    Tek dosya yaklaşımı küçük servisler için iş görür, ama bir avuçtan fazla endpoint'i olan her şey yapıya ihtiyaç duyar. En etkili bulduğum pattern, endpoint'leri feature bazlı statik sınıflarda organize etmektir:

    csharp
    class=class="code-string">"code-comment">// Features/Products/ProductEndpoints.cs
    public static class ProductEndpoints
    {
        public static RouteGroupBuilder MapProductEndpoints(this IEndpointRouteBuilder routes)
        {
            var group = routes.MapGroup(class="code-string">"/products").WithTags(class="code-string">"Products");
    
            group.MapGet(class="code-string">"/", GetAll);
            group.MapGet(class="code-string">"/{id:int}", GetById);
            group.MapPost(class="code-string">"/", Create).RequireAuthorization(class="code-string">"Admin");
            group.MapPut(class="code-string">"/{id:int}", Update).RequireAuthorization(class="code-string">"Admin");
            group.MapDelete(class="code-string">"/{id:int}", Delete).RequireAuthorization(class="code-string">"Admin");
    
            return group;
        }
    
        private static async Task<Ok<List<ProductDto>>> GetAll(
            IProductRepository repo, CancellationToken ct)
        {
            var products = await repo.GetAllAsync(ct);
            return TypedResults.Ok(products);
        }
    
        private static async Task<Results<Ok<ProductDto>, NotFound>> GetById(
            int id, IProductRepository repo, CancellationToken ct)
        {
            var product = await repo.GetByIdAsync(id, ct);
            return product is not null
                ? TypedResults.Ok(product)
                : TypedResults.NotFound();
        }
    
        class=class="code-string">"code-comment">// ... diğer handler'lar
    }
    
    class=class="code-string">"code-comment">// Program.cs temiz kalır
    var app = builder.Build();
    app.MapProductEndpoints();
    app.MapOrderEndpoints();
    app.MapUserEndpoints();
    app.Run();

    Bu yaklaşım, her feature için tüm route'ları tek bir yerde görme netliğini sunarken `Program.cs`'i bir composition root olarak temiz tutar. Her feature dosyası kendi route'larına, handler'larına ve yetkilendirme gereksinimlerine sahiptir.

    Auth, Validasyon ve Hata Yönetimi

    Kimlik Doğrulama ve Yetkilendirme

    csharp
    builder.Services.AddAuthentication().AddJwtBearer();
    builder.Services.AddAuthorizationBuilder()
        .AddPolicy(class="code-string">"Admin", policy => policy.RequireRole(class="code-string">"admin"))
        .AddPolicy(class="code-string">"ApiScope", policy => policy.RequireClaim(class="code-string">"scope", class="code-string">"api"));
    
    var app = builder.Build();
    
    class=class="code-string">"code-comment">// Route groupclass="code-string">'a toplu uygulama
    var api = app.MapGroup(class="code-string">"/api").RequireAuthorization(class="code-string">"ApiScope");
    
    class=class="code-string">"code-comment">// Belirli endpoint'ler için geçersiz kılma
    api.MapGet(class="code-string">"/public/health", () => Results.Ok(class="code-string">"Healthy")).AllowAnonymous();
    api.MapDelete(class="code-string">"/admin/cache", (ICacheService cache) => cache.Clear()).RequireAuthorization(class="code-string">"Admin");

    FluentValidation ile Merkezi Doğrulama

    csharp
    class=class="code-string">"code-comment">// Assemblyclass="code-string">'deki tüm validator'ları kaydet
    builder.Services.AddValidatorsFromAssemblyContaining<Program>();
    
    class=class="code-string">"code-comment">// Route group başına uygulanan generic validasyon filtresi
    public static RouteGroupBuilder WithValidation<T>(this RouteGroupBuilder group) where T : class
    {
        return group.AddEndpointFilter<ValidationFilter<T>>();
    }

    Global Hata Yönetimi

    csharp
    app.UseExceptionHandler(errorApp =>
    {
        errorApp.Run(async context =>
        {
            var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;
            var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
            logger.LogError(exception, class="code-string">"Yakalanmamış istisna");
    
            context.Response.StatusCode = exception switch
            {
                NotFoundException => class="code-number">404,
                ValidationException => class="code-number">400,
                UnauthorizedAccessException => class="code-number">403,
                _ => class="code-number">500
            };
    
            await context.Response.WriteAsJsonAsync(new ProblemDetails
            {
                Status = context.Response.StatusCode,
                Title = exception switch
                {
                    NotFoundException => class="code-string">"Kaynak bulunamadı",
                    ValidationException => class="code-string">"Doğrulama başarısız",
                    _ => class="code-string">"Bir hata oluştu"
                }
            });
        });
    });

    Minimal API'lerle kurduğum mikroservislerde, global exception handler ile validasyon için endpoint filter'ları birleştirmek hata senaryolarının büyük çoğunluğunu temiz bir şekilde karşılıyor. Handler beklenmeyen durumları yakalar, filter'lar ise öngörülebilir vakaları endpoint'e yakın bir noktada ele alır.

    Minimal APIs vs Controller: Karar Çerçevesi

    | Kriter | Minimal APIs | Controller'lar |

    |---|---|---|

    | Boilerplate | Çok düşük, lambda tabanlı | Orta, sınıf + attribute |

    | Başlangıç performansı | Daha hızlı, reflection yükü yok | Biraz daha yavaş |

    | Keşfedilebilirlik | Tüm route'lar tek dosyada veya feature modülünde | Controller sınıflarına dağılmış |

    | Model binding | Convention tabanlı, daha basit | Daha zengin, daha yapılandırılabilir |

    | Filtreler/middleware | Endpoint filter'lar (yeni, basit) | Action filter'lar (olgun, tam donanımlı) |

    | OpenAPI desteği | TypedResults ile yerleşik | Attribute odaklı, köklü |

    | Test edilebilirlik | Doğrudan fonksiyon çağrısı | Controller örnekleme gerektirir |

    | Ekip aşinalığı | Daha yeni paradigma | Bilinen MVC kalıbı |

    | Geniş API yüzeyleri | Organize etmek disiplin ister | Sınıf bazlı doğal yapı |

    | Content negotiation | Manuel kurulum | Yerleşik |

    Minimal API'leri tercih edin: Odaklı mikroservisler, BFF'ler veya sadelik ile başlangıç hızının MVC konvansiyonlarından daha önemli olduğu herhangi bir servis inşa ediyorsanız. Servisinizde 30-40'tan az endpoint varsa, Minimal API'ler muhtemelen daha temiz olacaktır.

    Controller'ları tercih edin: Karmaşık model binding, content negotiation gereksinimleri olan geniş bir API yüzeyiniz varsa veya ekibiniz MVC kalıplarına derinden yatırım yapmışsa. Controller'larda utanılacak bir şey yok; savaşta test edilmiş ve iyi anlaşılmış yapılardır.

    Hibrit yaklaşım da işe yarar: Basit CRUD ve webhook endpoint'leri için Minimal API'leri kullanırken, MVC konvansiyonlarından yararlanan karmaşık alanlar için controller'ları koruyabilirsiniz. ASP.NET Core aynı projede her ikisini de destekler.

    Sık Yapılan Minimal API Hataları

    1. İş Mantığını Handler'lara Koymak

    csharp
    class=class="code-string">"code-comment">// Kötü: handler her şeyi yapıyor
    app.MapPost(class="code-string">"/orders", async (CreateOrderRequest req, AppDbContext db) =>
    {
        class=class="code-string">"code-comment">// class="code-number">30 satır validasyon, iş kuralı ve persistence
        class=class="code-string">"code-comment">// Bu çok hızlı sürdürülemez hale gelir
    });
    
    class=class="code-string">"code-comment">// İyi: handler bir servise delege eder
    app.MapPost(class="code-string">"/orders", async (CreateOrderRequest req, IOrderService service) =>
    {
        var result = await service.PlaceOrderAsync(req);
        return result.IsSuccess
            ? TypedResults.Created($class="code-string">"/orders/{result.Value.Id}", result.Value)
            : TypedResults.BadRequest(result.Error);
    });

    2. CancellationToken'ı Görmezden Gelmek

    csharp
    class=class="code-string">"code-comment">// Kötü: iptal desteği yok
    app.MapGet(class="code-string">"/reports", async (IReportService service) =>
        await service.GenerateAsync());
    
    class=class="code-string">"code-comment">// İyi: iptali yay
    app.MapGet(class="code-string">"/reports", async (IReportService service, CancellationToken ct) =>
        await service.GenerateAsync(ct));

    3. Route Group Kullanmamak

    Her route'u üst düzeyde tanımlayıp `.RequireAuthorization()` ve `.WithTags()` çağrılarını tekrarlamak gürültülü, bakımı zor koda yol açar. Route group'lar tam olarak bu sorunu çözmek için var.

    4. Her Şey için `Results.Ok()` Kullanmak

    csharp
    class=class="code-string">"code-comment">// Kötü: oluşturma için bile her zaman class="code-number">200 döner
    app.MapPost(class="code-string">"/items", async (Item item, IItemService service) =>
        Results.Ok(await service.CreateAsync(item)));
    
    class=class="code-string">"code-comment">// İyi: anlamsal olarak doğru durum kodları kullan
    app.MapPost(class="code-string">"/items", async (Item item, IItemService service) =>
    {
        var created = await service.CreateAsync(item);
        return TypedResults.Created($class="code-string">"/items/{created.Id}", created);
    });

    5. Yapılandırılmış Loglama'yı Atlamak

    Minimal API'lerde MVC'deki gibi yerleşik action loglama yoktur. İstek/yanıt loglamayı middleware veya endpoint filter'lar aracılığıyla açıkça ekleyin, yoksa production'da kör hata ayıklama yaparsınız.

    Sonuç

    Minimal API'ler bir oyuncak veya kestirme yol değildir; .NET'te HTTP servisleri inşa etmenin olgun, production'a hazır bir yaklaşımıdır. Controller'lardan farkı sözdizimi ve seremonidir, mühendislik standartları değil. Temiz mimari, uygun validasyon, yapılandırılmış hata yönetimi ve düşünceli endpoint organizasyonu hâlâ gereklidir.

    Asıl kavranması gereken nokta, Minimal API'lerin yeteneği azaltmadan sürtünmeyi kaldırdığıdır. Controller tabanlı ASP.NET Core'da güvendiğiniz her özellik (DI, auth, OpenAPI, filter'lar, middleware) tamamen mevcuttur. Kazandığınız şey nettir: servisinizin tüm yüzey alanı görünür, birleştirilebilir ve akıl yürütmesi kolaydır.

    API yaklaşımınızı seçmenizde ve ölçeklenebilir bir endpoint yapısı tasarlamanızda size yardımcı olabilirim.

    İlgili Makaleler

    Flutter Projeniz mi Var?

    iOS, Android ve web için yüksek performanslı Flutter uygulamaları geliştiriyorum.

    İletişime Geç