• Aplikacje webowe
  • Minimal API w ASP.NET Core - kiedy wybrać je zamiast kontrolerów?

Minimal API w ASP.NET Core - kiedy wybrać je zamiast kontrolerów?

Bruno Krawczyk 15 lipca 2026
Szablon ASP.NET Core Empty do tworzenia aplikacji webowych. Idealny do rozpoczęcia projektu z minimal api.

Spis treści

Gdy trzeba szybko wystawić API dla aplikacji webowej, największym problemem często nie jest sam HTTP, lecz ilość kodu potrzebna do obsługi jednej prostej operacji. Minimal API w ASP.NET Core pozwala zdefiniować endpoint niemal bezpośrednio w pliku startowym, a jednocześnie zachować dostęp do wstrzykiwania zależności, autoryzacji, OpenAPI i testów. Pokażę, jak działa to podejście, gdzie sprawdza się najlepiej oraz w którym momencie warto rozważyć kontrolery.

Najważniejsze informacje o lekkim podejściu do API

  • Minimalne API ogranicza kod startowy i pozwala definiować endpointy za pomocą metod takich jak MapGet czy MapPost.
  • Wiązanie parametrów może automatycznie pobierać dane z trasy, zapytania, nagłówków, formularza, ciała żądania i kontenera DI.
  • Route groups, filtry endpointów i klasy wyników pomagają rozwijać aplikację bez zamieniania pliku Program.cs w trudny do utrzymania monolit.
  • Kontrolery nadal mają sens przy rozbudowanych projektach, zaawansowanej walidacji i zespołach pracujących w klasycznym modelu MVC.
  • Produkcja wymaga zadbania o autoryzację, walidację, dokumentację OpenAPI, obsługę błędów i testy integracyjne.

Schemat przedstawia strukturę minimal api, pokazując ścieżki i metody HTTP dla zasobów, np. /api/Values.

Czym jest Minimal API i co upraszcza

To sposób budowania HTTP API w ASP.NET Core, w którym endpoint definiuje się bez tworzenia osobnej klasy kontrolera i wielu metod pomocniczych. Najprostszy przykład wygląda tak:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/status", () => Results.Ok(new
{
    Service = "Catalog",
    Healthy = true
}));

app.Run();

Jedna metoda opisuje tutaj trasę, metodę HTTP i obsługę żądania. Framework uruchamia serwer, dopasowuje ścieżkę i serializuje obiekt do JSON-a. Dla małego endpointu jest to bardzo czytelne, bo kod nie rozdziela sztucznie informacji, które i tak dotyczą jednej operacji.

Nie oznacza to jednak, że aplikacja jest pozbawiona architektury. Nadal możesz korzystać z middleware, konfiguracji, logowania, Entity Framework Core, autoryzacji, cache, OpenAPI i dependency injection. Różnica polega na tym, że wiele elementów nie wymaga dodatkowej warstwy kontrolera.

Jak działa obsługa parametrów

Parametry metody mogą być pobierane z różnych części żądania. Liczba całkowita w ścieżce zostanie odczytana z route, prosty typ może pochodzić z query stringa, a obiekt złożony najczęściej z ciała żądania.

app.MapGet("/products/{id:int}", (int id, IProductService service) =>
{
    var product = service.GetById(id);

    return product is null
        ? Results.NotFound()
        : Results.Ok(product);
});

W tym przykładzie id pochodzi z adresu, a IProductService z kontenera zależności. To właśnie automatyczne wiązanie parametrów sprawia, że kod jest krótki, ale nie musi być prymitywny.

Pierwszy endpoint od uruchomienia do odpowiedzi

Nowy projekt możesz utworzyć z szablonu Web API, a potem zacząć od niewielkiego kontraktu. Dla przykładowej listy zadań wystarczy kilka endpointów opisujących odczyt, dodawanie, aktualizację i usuwanie danych.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton();

var app = builder.Build();

app.MapGet("/todos", (TodoStore store) =>
    Results.Ok(store.GetAll()));

app.MapGet("/todos/{id:int}", (int id, TodoStore store) =>
{
    var todo = store.Get(id);

    return todo is null
        ? Results.NotFound()
        : Results.Ok(todo);
});

app.MapPost("/todos", (CreateTodoRequest request, TodoStore store) =>
{
    var todo = store.Create(request);
    return Results.Created($"/todos/{todo.Id}", todo);
});

app.Run();

Warto zwrócić uwagę na kod statusu HTTP. 200 OK pasuje do odczytu, 201 Created do utworzenia zasobu, a 404 Not Found informuje klienta, że wskazany element nie istnieje. Taka precyzja jest ważniejsza niż skrócenie kodu o kilka linii.

Wyniki typowane zamiast niejasnego zwracania danych

Przy prostych przykładach Results.Ok jest wystarczające. W większej aplikacji lepiej opisywać możliwe odpowiedzi typami, ponieważ poprawia to czytelność i dokumentację OpenAPI.

app.MapGet("/todos/{id:int}",
    Results, NotFound> (int id, TodoStore store) =>
{
    var todo = store.Get(id);

    return todo is null
        ? TypedResults.NotFound()
        : TypedResults.Ok(todo);
});

Takie podejście jasno pokazuje, że endpoint zwraca albo zadanie, albo odpowiedź 404. Przy API używanym przez frontend, aplikację mobilną lub inne zespoły zmniejsza to liczbę nieporozumień dotyczących kontraktu.

Jak rozwijać aplikację bez bałaganu

Największa pułapka polega na tym, że pierwszy endpoint jest prosty, więc dokładamy kolejny bez żadnego planu. Po kilku tygodniach cały Program.cs może mieć setki linii. Sam zapis endpointu nie gwarantuje dobrej architektury, dlatego granice trzeba wprowadzić świadomie.

Grupuj endpointy według modułów

MapGroup pozwala wspólnie ustawić prefiks, tagi, autoryzację albo filtry dla powiązanych tras. Dzięki temu katalog produktów i obsługa użytkowników nie mieszają się w jednym płaskim zbiorze metod.

var products = app.MapGroup("/api/products")
    .WithTags("Products")
    .RequireAuthorization();

products.MapGet("/", (IProductService service) =>
    TypedResults.Ok(service.GetAll()));

products.MapGet("/{id:int}", (int id, IProductService service) =>
{
    var product = service.GetById(id);

    return product is null
        ? TypedResults.NotFound()
        : TypedResults.Ok(product);
});

Gdy grupa zaczyna rosnąć, przenoszę jej konfigurację do metody rozszerzającej, na przykład MapProductEndpoints. Zyskuję wtedy modułową organizację, ale bez powrotu do ciężkiego modelu kontrolerów.

Oddziel kontrakt od logiki biznesowej

Handler endpointu powinien koordynować operację, a nie zawierać całej logiki domenowej, zapytań SQL i reguł biznesowych. Dobry sygnał ostrzegawczy pojawia się wtedy, gdy jedna lambda ma więcej niż kilkanaście linii albo zaczyna wykonywać kilka niezależnych zadań.

public sealed record CreateProductRequest(
    string Name,
    decimal Price);

public static class ProductEndpoints
{
    public static void MapProductEndpoints(this IEndpointRouteBuilder app)
    {
        app.MapPost("/api/products",
            async (CreateProductRequest request,
                   IProductService service,
                   CancellationToken cancellationToken) =>
            {
                var product = await service.CreateAsync(
                    request, cancellationToken);

                return TypedResults.Created(
                    $"/api/products/{product.Id}",
                    product);
            });
    }
}

W praktyce najczęściej rozdzielam projekt na kontrakty, endpointy, usługi i dostęp do danych. Sama składnia pozostaje lekka, ale odpowiedzialności nie zlewają się w jeden plik.

Waliduj dane przy wejściu

Automatyczne zbindowanie obiektu do JSON-a nie oznacza jeszcze, że dane są poprawne. Cena może być ujemna, nazwa pusta, a identyfikator spoza oczekiwanego zakresu. Walidację warto wykonać przed wejściem do logiki biznesowej, korzystając z własnych reguł, biblioteki walidacyjnej lub mechanizmu dostępnego w danej wersji ASP.NET Core.

Najważniejsze jest, aby klient otrzymał spójny błąd 400, a aplikacja nie próbowała zapisywać niepoprawnego obiektu. Walidacja w bazie danych jest potrzebna, ale nie powinna być jedyną linią obrony.

Minimalne API czy kontrolery

Oba modele korzystają z tego samego ASP.NET Core, middleware i kontenera zależności. Różnica dotyczy głównie sposobu organizacji punktów wejścia. W nowych, niewielkich usługach zwykle zaczynam od endpointów funkcyjnych, ale nie traktuję tego jako zasady obowiązującej w każdym projekcie.

Kryterium Minimalne API Kontrolery
Start projektu Szybki, mało kodu infrastrukturalnego Więcej klas i konwencji
Małe serwisy i prototypy Bardzo dobry wybór Często nadmiarowa struktura
Duże moduły biznesowe Wymagają własnego podziału na pliki i moduły Naturalny podział przez kontrolery i akcje
Rozbudowana walidacja i rozszerzenia MVC Możliwe, ale część rozwiązań trzeba skonfigurować samodzielnie Więcej funkcji dostępnych w standardowym modelu
Wydajność Mniejszy narzut potoku obsługi Większy narzut, zwykle nieistotny przy typowym CRUD
Praca zespołowa Duża swoboda, ale trzeba ustalić konwencje Więcej gotowych wzorców i przewidywalna struktura

Microsoft rekomenduje minimalne API jako punkt wyjścia dla nowych projektów, ale ta rekomendacja nie oznacza, że kontrolery są przestarzałe. Jeżeli aplikacja korzysta z rozbudowanych funkcji MVC, OData, specyficznych binderów albo zespołowi łatwiej utrzymać klasyczny model, kontrolery mogą być rozsądniejszą decyzją.

Moja praktyczna reguła jest prosta. Dla niezależnego mikroserwisu, backendu dla jednego frontendu lub niewielkiego API wybieram lżejszy model. Dla dużej aplikacji, w której każdy moduł ma skomplikowane reguły i własny cykl rozwoju, ważniejsza od liczby linii jest przewidywalność struktury.

Co sprawdzić przed wdrożeniem

Krótki kod endpointu nie zwalnia z pracy, którą trzeba wykonać w aplikacji produkcyjnej. Najczęstszy błąd polega na tym, że przykład demonstracyjny trafia do środowiska publicznego bez autoryzacji, limitów i jednolitej obsługi błędów.

Autoryzacja i polityki dostępu

Autoryzację można przypisać pojedynczej trasie albo całej grupie. To drugie rozwiązanie zmniejsza ryzyko, że ktoś doda nowy endpoint i zapomni zabezpieczyć go atrybutem lub konfiguracją.

var admin = app.MapGroup("/api/admin")
    .RequireAuthorization("Administrators");

admin.MapDelete("/users/{id:int}",
    (int id, IUserService service) =>
    {
        service.Delete(id);
        return TypedResults.NoContent();
    });

Oprócz samego logowania sprawdź uprawnienia do konkretnego zasobu. Użytkownik może być uwierzytelniony, ale nadal nie powinien mieć dostępu do danych innego klienta.

OpenAPI, logowanie i testy

Dokumentacja OpenAPI opisuje trasy, parametry i odpowiedzi, dzięki czemu frontend oraz narzędzia testowe nie muszą zgadywać kontraktu. W aktualnym ASP.NET Core można generować dokument dla endpointów za pomocą pakietu Microsoft.AspNetCore.OpenApi.

Do testów jednostkowych warto wyciągać logikę biznesową do osobnych serwisów. Same endpointy najlepiej sprawdzać również testami integracyjnymi, które uruchamiają aplikację i wysyłają prawdziwe żądania HTTP. Wtedy łatwiej wykryć problemy z routingiem, serializacją, autoryzacją i kodami odpowiedzi.

Przeczytaj również: Blazor Server w .NET - kiedy warto wybrać interaktywność serwerową?

Obsługa błędów i anulowanie żądań

Nie zwracaj klientowi stosu wyjątków ani przypadkowych komunikatów z bazy danych. Skonfiguruj wspólny mechanizm problem details i loguj szczegóły po stronie serwera. Dla operacji asynchronicznych przekazuj także CancellationToken, aby przerwane żądanie nie wykonywało niepotrzebnej pracy.

Przed wdrożeniem sprawdzam jeszcze timeouty, limity rozmiaru żądania, CORS, sekrety poza kodem oraz zachowanie przy błędach zależności zewnętrznych. Te elementy nie są efektowne, ale zwykle robią większą różnicę niż dalsze skracanie handlerów.

Jaką decyzję podjąć przy nowej aplikacji

To podejście ma sens wtedy, gdy zależy Ci na szybkim starcie, małej liczbie endpointów i bezpośrednim przepływie od żądania do odpowiedzi. Nie musisz jednak wybierać między chaosem w jednym pliku a ciężką strukturą kontrolerów. Grupy tras, metody rozszerzające i osobne serwisy pozwalają zachować lekkość bez rezygnacji z porządku.

Na początek zbuduj jeden rzeczywisty przypadek, dodaj poprawne kody HTTP, walidację i test integracyjny. Jeżeli wraz ze wzrostem projektu pojawią się potrzeby typowe dla MVC, przejście do kontrolerów albo połączenie obu stylów pozostaje możliwe. Najlepsza architektura to ta, którą zespół potrafi konsekwentnie rozwijać, a nie ta, która wygląda najkrócej w pierwszym przykładzie.

FAQ - Najczęstsze pytania

Parametry endpointu mogą być pobierane z trasy, query stringa, nagłówków, formularza, ciała żądania oraz kontenera dependency injection. Na przykład identyfikator produktu może pochodzić z adresu, a serwis z kontenera zależności.

Route groups pozwalają wspólnie ustawić prefiks, tagi, autoryzację i filtry dla powiązanych tras. Gdy grupa rośnie, jej konfigurację można przenieść do metody rozszerzającej, takiej jak MapProductEndpoints, aby zachować modułową organizację.

Minimal API dobrze sprawdza się w małych serwisach, prototypach, niezależnych mikroserwisach i backendach dla jednego frontendu. Kontrolery mogą być rozsądniejszym wyborem przy dużych modułach biznesowych, rozbudowanej walidacji, OData, specyficznych binderach MVC lub zespołach preferujących klasyczną strukturę.

Należy zadbać o autoryzację, walidację danych, spójne kody HTTP, dokumentację OpenAPI, logowanie i wspólną obsługę błędów opartą na problem details. Endpointy warto sprawdzać testami integracyjnymi, a w operacjach asynchronicznych przekazywać CancellationToken; trzeba też zweryfikować CORS, timeouty, limity żądań i sekrety poza kodem.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

minimal api
routing
openapi
walidacja
kontrolery
Autor Bruno Krawczyk
Bruno Krawczyk
Mam na imię Bruno i od 8 lat zgłębiam tajniki programowania w ekosystemie .NET, chmury Azure oraz sztucznej inteligencji. Moja przygoda z technologią zaczęła się od ciekawości, jak złożone systemy mogą ułatwiać codzienne życie i rozwiązywać realne problemy. Dziś moją misją jest dzielenie się tą wiedzą, starając się przybliżyć nawet najbardziej skomplikowane zagadnienia w sposób zrozumiały i przystępny dla każdego. W moich artykułach na kursdotnet.pl skupiam się na praktycznych aspektach, analizuję najnowsze trendy i weryfikuję informacje, aby dostarczyć Wam treści, które są nie tylko dokładne i aktualne, ale przede wszystkim użyteczne w Waszej własnej ścieżce rozwoju technologicznego.

Udostępnij artykuł

Napisz komentarz