Tworzenie aplikacji webowej często zaczyna się od prostego pytania: jak udostępnić dane frontendowi, aplikacji mobilnej albo innemu systemowi? Dobrze zaprojektowane .NET API rozwiązuje ten problem, ale dopiero połączenie routingu, walidacji, bezpieczeństwa, dokumentacji i poprawnej obsługi błędów daje usługę gotową do pracy. Pokażę, jak budować API w ASP.NET Core, jak konsumować je przez HttpClient oraz na co uważać przed wdrożeniem.
API w .NET łączy prostotę tworzenia z wymaganiami produkcyjnego systemu
- ASP.NET Core pozwala tworzyć REST API zarówno przez Minimal APIs, jak i kontrolery.
- DTO i walidacja chronią kontrakt usługi przed przypadkowym ujawnieniem modelu bazy danych.
- IHttpClientFactory pomaga bezpiecznie konsumować zewnętrzne API i ogranicza problemy z połączeniami.
- OpenAPI upraszcza dokumentowanie, testowanie i generowanie klientów.
- JWT, logowanie i testy są potrzebne, aby usługa działała stabilnie poza środowiskiem lokalnym.

Co naprawdę daje API w aplikacji .NET
API, czyli interfejs programistyczny aplikacji, udostępnia funkcje i dane za pomocą jasno określonych adresów HTTP. Klient wysyła żądanie, na przykład GET /api/products/15, a serwer zwraca odpowiedź z kodem statusu i najczęściej dokumentem JSON.
W aplikacjach webowych API oddziela warstwę prezentacji od logiki biznesowej. Dzięki temu ten sam backend może obsłużyć stronę React, aplikację mobilną, panel administracyjny i integrację z systemem zewnętrznym. To właśnie ta niezależność jest zwykle ważniejsza niż sam wybór konkretnego frameworka.
W 2026 roku nowe projekty mogą korzystać z .NET 10, który jest wydaniem LTS, czyli wersją z dłuższym okresem wsparcia. Nie oznacza to jednak, że każda aplikacja wymaga natychmiastowej migracji. Przy istniejącym systemie większą wartość często przynosi uporządkowanie kontraktów, testów i obserwowalności niż sama zmiana wersji platformy.
REST, JSON i kody HTTP
Najczęściej spotkasz API REST, w którym zasoby są reprezentowane przez adresy i operacje HTTP. Typowy zestaw wygląda tak:
| Metoda | Przykład | Zastosowanie |
|---|---|---|
| GET | /api/products | Pobieranie danych |
| POST | /api/products | Tworzenie zasobu |
| PUT | /api/products/15 | Pełna aktualizacja |
| PATCH | /api/products/15 | Częściowa aktualizacja |
| DELETE | /api/products/15 | Usuwanie zasobu |
Sam JSON nie jest jeszcze dobrym kontraktem. O jakości API decydują również statusy HTTP, nazewnictwo endpointów, format błędów, reguły walidacji i przewidywalność odpowiedzi. Zwracanie kodu 200 dla każdego przypadku utrudnia pracę klientom i zwykle kończy się dodatkowymi warunkami w kodzie frontendowym.
Jak zbudować pierwsze API w ASP.NET Core
Najprostszy projekt utworzysz poleceniem dotnet new webapi. ASP.NET Core dostarcza routing, wstrzykiwanie zależności, serializację JSON, middleware i mechanizmy autoryzacji, więc nie trzeba składać całego stosu od zera.
Minimalny endpoint może wyglądać tak:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.MapGet("/api/products/{id:int}", (int id) =>
{
var product = new
{
Id = id,
Name = "Klawiatura mechaniczna",
Price = 299.99m
};
return Results.Ok(product);
});
app.Run();Ten przykład jest mały, ale pokazuje kilka ważnych elementów. Ograniczenie {id:int} sprawia, że trasa pasuje tylko do liczby, a Results.Ok zwraca odpowiedź z właściwym kodem HTTP. W realnej aplikacji dane nie powinny być tworzone w endpointcie. Logikę przeniósłbym do serwisu, a dostęp do bazy do osobnej warstwy.
DTO zamiast bezpośredniego modelu encji
DTO, czyli Data Transfer Object, jest obiektem przeznaczonym do przesyłania danych przez API. To drobna decyzja, która ma duże znaczenie. Chroni przed ujawnieniem pól technicznych, pozwala zmieniać strukturę bazy bez łamania klientów i ogranicza ryzyko niekontrolowanej aktualizacji danych.
public sealed record ProductResponse(
int Id,
string Name,
decimal Price);
public sealed record CreateProductRequest(
string Name,
decimal Price);Nie zwracałbym encji Entity Framework Core bezpośrednio z kontrolera, szczególnie gdy zawiera relacje, pola audytowe albo dane wewnętrzne. DTO jest granicą między aplikacją a światem zewnętrznym, a granice warto mieć pod kontrolą.
Minimal APIs czy kontrolery
Minimal APIs dobrze sprawdzają się przy małych usługach, prostych endpointach i mikroserwisach. Kontrolery dają więcej struktury, filtrów i znanych konwencji, dlatego w większych systemach często łatwiej utrzymać je przez kilka lat.
| Kryterium | Minimal APIs | Kontrolery |
|---|---|---|
| Start projektu | Bardzo szybki | Wymaga większej struktury |
| Mała usługa | Bardzo dobry wybór | Może być zbyt rozbudowany |
| Duże API | Wymaga dyscypliny organizacyjnej | Łatwiejsze grupowanie odpowiedzialności |
| Filtry i konwencje | Dostępne, ale mniej formalne | Dobrze rozwinięte |
| Testowanie | Proste przy niewielkiej liczbie tras | Wygodne przy rozbudowanej domenie |
Moja praktyczna reguła jest prosta. Dla kilku endpointów wybrałbym Minimal APIs, a gdy pojawiają się rozbudowane reguły biznesowe, wiele wersji kontraktu i duży zespół, sięgnąłbym po kontrolery. Sam styl deklarowania tras nie naprawi złej architektury, dlatego ważniejszy jest podział na endpointy, serwisy i warstwę domenową.
Jak konsumować API przez HttpClient
Tworzenie usługi to tylko połowa pracy. Aplikacja .NET często musi pobierać dane z płatności, magazynu, systemu CRM albo własnego mikroserwisu. Do takich połączeń używam IHttpClientFactory, ponieważ centralizuje konfigurację klientów i pomaga zarządzać cyklem życia połączeń.
builder.Services.AddHttpClient(client =>
{
client.BaseAddress = new Uri("https://catalog.internal/");
client.Timeout = TimeSpan.FromSeconds(10);
});
public sealed class CatalogClient(HttpClient httpClient)
{
public async Task GetProductsAsync(
CancellationToken cancellationToken)
{
return await httpClient.GetFromJsonAsync(
"api/products",
cancellationToken) ?? [];
}
} Tworzenie nowego HttpClient przy każdym żądaniu może prowadzić do wyczerpania gniazd i problemów z połączeniami przy większym ruchu. Z drugiej strony jeden klient trzymany bez odpowiedniej konfiguracji przez bardzo długi czas może nie reagować prawidłowo na zmiany DNS. Fabryka klientów rozwiązuje ten problem przez zarządzanie handlerami i pozwala przypisać konfigurację do konkretnej integracji.
Timeout, retry i anulowanie żądania
Nie każde nieudane żądanie powinno być ponawiane. Retry ma sens przy błędach przejściowych, takich jak chwilowy problem sieci, kod 408 albo wybrane odpowiedzi 5xx. Ponawianie żądania POST bez sprawdzenia idempotencji może natomiast utworzyć ten sam rekord dwa razy.
W systemach produkcyjnych konfiguruję timeout, limit prób i exponential backoff, czyli stopniowe wydłużanie przerw między próbami. Do tego przekazuję CancellationToken, aby przerwać pracę, gdy użytkownik zamknął stronę albo żądanie nadrzędne już wygasło.
Przy wielu integracjach dobrze działa osobna klasa klienta dla każdego systemu. Zamiast rozsypywać adresy URL i nagłówki po kontrolerach, mam jedno miejsce odpowiedzialne za autoryzację, serializację, logowanie i interpretację odpowiedzi.
Bezpieczeństwo, kontrakt i obsługa błędów
Publiczne API powinno wymagać uwierzytelnienia, chyba że dostęp anonimowy jest świadomą decyzją. W aplikacjach usługowych najczęściej stosuje się tokeny JWT Bearer. Token potwierdza tożsamość klienta, a autoryzacja określa, do jakich operacji ma on prawo.
Nie wystarczy sprawdzić, czy token istnieje. Trzeba zweryfikować jego podpis, wystawcę, odbiorcę i czas ważności, a także rozdzielić role od uprawnień biznesowych. Endpoint pozwalający zmienić cenę produktu powinien mieć bardziej restrykcyjne zasady niż endpoint publicznie zwracający katalog.
Walidacja danych wejściowych
Walidacja powinna odbywać się przy granicy systemu. Sprawdź wymagane pola, zakresy liczb, długość tekstu i format identyfikatorów, zanim dane trafią do serwisu lub bazy. Walidacja nie zastępuje reguł biznesowych, ale pozwala odrzucić oczywiście błędne żądania wcześniej i czytelniej.
Nie zwracaj klientowi szczegółów wyjątków, nazw tabel ani fragmentów zapytań SQL. W logach potrzebujesz pełnego kontekstu dla zespołu, natomiast odpowiedź HTTP powinna zawierać bezpieczny komunikat i identyfikator korelacyjny, który ułatwi znalezienie błędu.
Przeczytaj również: Jak sprawdzać, czy plik istnieje w Node.js?
Spójny format odpowiedzi
Dla błędów warto używać jednolitego formatu, na przykład Problem Details. Klient wie wtedy, gdzie znaleźć tytuł, kod, szczegóły i identyfikator problemu. Własne formaty błędów też są możliwe, ale mieszanie kilku schematów w jednej usłudze szybko utrudnia integrację.
Dokumentacja OpenAPI powinna powstawać razem z kodem. Narzędzia oparte na tym standardzie mogą generować interaktywny opis endpointów, ułatwiać testy i tworzyć typowanych klientów. W środowisku produkcyjnym ograniczyłbym dostęp do dokumentacji albo zabezpieczył ją autoryzacją, szczególnie gdy opisuje operacje administracyjne.
Co sprawdzić przed wdrożeniem API
Lokalnie działający endpoint nie oznacza jeszcze gotowej usługi. Przed wdrożeniem sprawdzam przede wszystkim kontrakt, bezpieczeństwo, zachowanie pod obciążeniem i sposób diagnozowania problemów.
- Testy jednostkowe sprawdzają reguły biznesowe bez uruchamiania całej aplikacji.
- Testy integracyjne potwierdzają routing, bazę danych, serializację i statusy HTTP.
- Testy kontraktowe chronią klienta przed przypadkową zmianą struktury odpowiedzi.
- Logi strukturalne pozwalają wyszukiwać błędy po identyfikatorze żądania, użytkowniku lub operacji.
- Metryki pokazują opóźnienia, liczbę błędów, obciążenie i najwolniejsze endpointy.
- Rate limiting ogranicza nadużycia i chroni kosztowne operacje.
Nie pomijałbym wersjonowania, jeśli z API korzystają klienci spoza naszego zespołu. Zmiana nazwy pola albo znaczenia statusu może zepsuć aplikację mobilną, której nie da się wdrożyć w tej samej chwili. Czasem wystarczy wersjonowanie ścieżki, a czasem lepszy będzie nagłówek lub osobny kontrakt, ale decyzję trzeba podjąć przed pierwszą dużą zmianą.
Wydajność również warto mierzyć zamiast zgadywać. Najczęstsze problemy to zapytania N+1, zwracanie zbyt dużych kolekcji, brak paginacji i niepotrzebne pobieranie kolumn. Paginacja, filtrowanie i limit rozmiaru odpowiedzi zwykle dają większą poprawę niż przedwczesne komplikowanie architektury.
Dobry punkt startu dla własnej usługi
Najrozsądniejszy pierwszy projekt to niewielkie API z jednym zasobem, na przykład produktami albo zadaniami. Zacząłbym od pięciu operacji CRUD, DTO, walidacji i kilku testów integracyjnych, a dopiero później dodałbym bazę danych, autoryzację i integrację z zewnętrznym systemem.
Moja najważniejsza rada jest praktyczna: nie traktuj endpointu jako całej aplikacji. Endpoint powinien przyjąć żądanie i zwrócić odpowiedź, natomiast reguły biznesowe, dostęp do danych, bezpieczeństwo i odporność na błędy powinny mieć własne miejsca. Taki podział początkowo wymaga kilku dodatkowych klas, ale bardzo szybko zwraca się przy pierwszej zmianie wymagań.
ASP.NET Core daje wszystkie podstawowe narzędzia do budowy nowoczesnych usług webowych. O jakości rozwiązania decyduje jednak nie liczba użytych bibliotek, lecz klarowny kontrakt, rozsądne granice odpowiedzialności, bezpieczne konsumowanie zależności i testy, które pokazują, że API działa również wtedy, gdy świat zewnętrzny przestaje być idealny.
