Ale REST to nie tylko "używaj HTTP". To zbiór konwencji i zasad, które sprawiają, że API jest intuicyjne, przewidywalne i łatwe w użyciu.
W tym artykule poznasz zasady projektowania — zarówno jako konsument (frontend), jak i twórca (backend). Szerszy kontekst aplikacyjny znajdziesz w przewodniku Backend dla frontendowca: serwer, bazy danych i API.
Zasady REST API, które stabilizują projekt
REST opisuje sześć właściwości, które razem tworzą architekturę zwaną Representational State Transfer. Warto je znać nie po to, żeby cytatować ze specyfikacji, ale żeby rozumieć, dlaczego pewne decyzje projektowe są lepsze od innych.
Client-server w REST API
Klient (frontend) i serwer (backend) są od siebie oddzielone i komunikują się przez HTTP. Ta separacja oznacza, że frontend może ewoluować niezależnie od backendu — wystarczy zachować kontrakt API. Możesz na przykład przepisać całe na inny framework bez dotykania backendu, albo wymienić silnik bazy danych bez wpływu na klientów.
Stateless API: brak sesji po stronie serwera
Każdy request powinien zawierać wszystko, czego serwer potrzebuje do jego obsługi — bez polegania na stanie sesji po stronie serwera. W praktyce wiele API łączy to z tokenami w nagłówkach Authorization, co pozwala na stateless uwierzytelnianie. Jeśli API korzysta z sesji serwerowych lub cookies, jest to technicznie odejście od czystego REST, ale w realu to częsty kompromis — ważniejsze jest, żeby kontrakt był spójny i dobrze udokumentowany.
Cacheable: cache HTTP w REST API
Odpowiedzi powinny jawnie informować klientów, czy można je cachować i jak długo. Nagłówki Cache-Control i ETag to podstawa — dzięki nim przeglądarka, lub reverse proxy mogą serwować odpowiedzi bez angażowania backendu. Dla API publicznych to realny wpływ na koszty infrastruktury i czas odpowiedzi.
Uniform Interface: spójny kontrakt REST API
Jednolity interfejs to serce REST — zasoby identyfikowane przez URL, operacje przez metody HTTP, standaryzowane kody statusu. Dzięki tej jednolitości deweloper integrujący nowe API może w dużej mierze przewidzieć jego zachowanie bez czytania dokumentacji od deski do deski.
Layered System: warstwy w architekturze API
Klient nie powinien zakładać, że komunikuje się bezpośrednio z serwerem docelowym. Pomiędzy nimi mogą stać load balancery, cache'e, API gateway czy reverse proxy — i to jest poprawna, wręcz pożądana architektura. Dobry design REST uwzględnia, że warstwa pośrednia może np. cachować GET-y albo rozdzielać ruch.
Code on Demand jako opcjonalna zasada REST
Serwer może opcjonalnie przekazywać klientowi wykonywalny kod (np. JavaScript). W praktyce ta zasada jest rzadko stosowana i często pomijana w opisach REST.
Struktura URL w REST API
Zasoby w URL, czyli resources w REST API
Pierwsza zasada, której złamanie natychmiast widać w code review: URL powinien nazywać zasób, a nie akcję. Metoda HTTP jest od opisywania co robisz — URL jest od opisywania z czym.
Wzorzec z czasownikami pochodzi z epoki SOAP i RPC — w REST nie ma dla niego miejsca. Jeśli masz ochotę napisać /deleteUser/123, to sygnał, że powinieneś napisać DELETE /users/123.
Kolekcje i pojedyncze zasoby w endpointach
Ten podział jest fundamentem REST: kolekcja zasobów ma inny URL niż pojedynczy zasób, a operacje na obu wyglądają inaczej.
POST /users tworzy nowy zasób w kolekcji. DELETE /users/123 usuwa konkretny zasób z kolekcji. Frontend integrujący takie API może wywnioskować semantykę bez czytania dokumentacji.
Zagnieżdżone zasoby i relacje w REST API
Zagnieżdżenie w URL wyraża relację między zasobami — komentarze należą do posta, posty należą do użytkownika:
Zasada praktyczna: zatrzymaj się na maksymalnie 2-3 poziomach. Głębsze drzewo (/users/123/posts/456/comments/789/replies) jest trudne do czytania i sugeruje, że relacja powinna być modelowana inaczej — np. przez filtrowanie: GET /replies?commentId=789.
Konwencje nazewnictwa endpointów REST
Konsekwencja w nazewnictwie jest ważniejsza niż wybór konkretnej konwencji — ale warto wybrać tę, którą web de facto stosuje:
Liczba mnoga dla kolekcji odzwierciedla fakt, że zasób to zbiór rekordów, nie jeden rekord. Kebab-case jest standardem webowym — camelCase jest dla właściwości JSON, nie segmentów URL.
Metody HTTP i CRUD w REST API
Metoda HTTP wyraża intencję operacji — to ta część requestu, która odpowiada na pytanie "co chcesz zrobić z tym zasobem?". Mapowanie na jest intuicyjne, ale ma kilka niuansów, które warto znać zanim zaczniesz projektować API.
| Metoda | CRUD | Opis | Idempotentna? | Przykład |
|---|---|---|---|---|
| GET | Read | Pobierz zasób | Tak | GET /users/123 |
| POST | Create | Utwórz zasób | Nie | POST /users |
| PUT | Update | Zastąp zasób | Tak | PUT /users/123 |
| PATCH | Update | Częściowa aktualizacja | Zależy | PATCH /users/123 |
| DELETE | Delete | Usuń zasób | Tak | DELETE /users/123 |
Idempotencja oznacza, że wielokrotne wywołanie tej samej operacji daje ten sam efekt. Jeśli klient wyśle DELETE /users/123 dwa razy — drugi raz powinien dostać 404, ale stan serwera jest ten sam. Przy POST każde wywołanie tworzy nowy zasób — retry bez zabezpieczenia to duplikat.
GET w REST API — pobieranie danych
POST w REST API — tworzenie zasobu
PUT w REST API — pełna aktualizacja zasobu
PUT zazwyczaj oznacza pełną reprezentację zasobu — jeśli nie przekażesz jakiegoś pola, serwer powinien je wyzerować lub usunąć. To jest często źródło bugów, gdy frontend wysyła tylko zmodyfikowane pola, a serwer traktuje brakujące jako "usuń". Dlatego PUT warto stosować tylko wtedy, gdy faktycznie chcesz zastąpić cały zasób.
PATCH w REST API — częściowa aktualizacja zasobu
PATCH aktualizuje tylko przekazane pola. To najwygodniejsza metoda dla typowych operacji — zmiana emaila, statusu, jednej właściwości — bez ryzyka przypadkowego wyzerowania reszty obiektu. Warto jednak pamiętać, że semantyka PATCH nie jest tak rygorystycznie ustandaryzowana jak PUT — różne API implementują ją różnie, dlatego dokładne zachowanie powinno być opisane w dokumentacji.
DELETE w REST API — usuwanie zasobu
Kody statusu HTTP w REST API
Kody statusu to pierwsza linia komunikacji między serwerem a klientem — zanim frontend przeczyta body odpowiedzi, sprawdza kod. Właściwe kody pozwalają frontendowi podjąć decyzję (pokaż błąd, przekieruj na login, odśwież token) bez parsowania treści. Błędne kody — np. 200 z błędem w body — łamią tę konwencję i utrudniają cachowanie, obsługę błędów i debugowanie.
Statusy 2xx — poprawna odpowiedź API
| Kod | Znaczenie | Kiedy |
|---|---|---|
| 200 | OK | GET, PUT, PATCH sukces |
| 201 | Created | POST sukces (nowy zasób) |
| 204 | No Content | DELETE sukces, brak body |
201 Created powinien pojawić się przy każdym POST, który tworzy zasób. Warto przy nim zwracać nagłówek Location z URL nowego zasobu — klient wie wtedy, gdzie sięgnąć po szczegóły bez dodatkowego GET-a. 204 No Content jest właściwą odpowiedzią na DELETE — operacja zakończyła się sukcesem, ale nie ma nic do zwrócenia.
Statusy 4xx — błędy klienta w API
| Kod | Znaczenie | Kiedy |
|---|---|---|
| 400 | Bad Request | Nieprawidłowe dane |
| 401 | Unauthorized | Brak/nieprawidłowy token |
| 403 | Forbidden | Brak uprawnień |
| 404 | Not Found | Zasób nie istnieje |
| 409 | Conflict | Konflikt (duplikat) |
| 422 | Unprocessable Entity | Błąd walidacji |
| 429 | Too Many Requests | Rate limit |
Dwa kody, które regularnie powodują zamieszanie:
401 vs 403 — 401 znaczy "nie wiem kim jesteś, zaloguj się", 403 znaczy "wiem kim jesteś, ale nie wolno Ci". Różnica jest fundamentalna dla frontend handlera: przy 401 powinno nastąpić przekierowanie na login lub odświeżenie tokenu, przy 403 wystarczy pokazać komunikat o braku uprawnień.
400 vs 422 — 400 to ogólny błąd formatu requestu (niepoprawny JSON, brakujące wymagane nagłówki), 422 to poprawny syntaktycznie request, który nie przeszedł walidacji biznesowej (np. email jest stringiem, ale nie ma znaku @). W praktyce wiele API używa tylko 400 dla obu przypadków — ważniejsze niż wybór kodu jest spójny, szczegółowy format błędu w body.
409 Conflict jest właściwy przy próbie stworzenia duplikatu (użytkownik z tym samym emailem już istnieje) lub przy konflikcie optymistycznego lockingu.
Statusy 5xx — błędy serwera w API
| Kod | Znaczenie | Kiedy |
|---|---|---|
| 500 | Internal Server Error | Nieoczekiwany błąd |
| 502 | Bad Gateway | Problem z upstream |
| 503 | Service Unavailable | Serwer przeciążony |
Błędy 5xx zawsze należą do serwera — klient nie może z nimi nic zrobić poza poczekaniem i ponowieniem. 503 Service Unavailable powinien być zwracany z nagłówkiem Retry-After, żeby klient wiedział, kiedy próbować ponownie. Nigdy nie zwracaj 5xx dla błędów walidacji czy braku uprawnień — to 4xx, i to ważne rozróżnienie przy monitoringu i alertach.
Format odpowiedzi JSON w REST API
Dobry format odpowiedzi to taki, którego klient nie musi zgadywać. Jeśli sukces wygląda inaczej przy GET a inaczej przy POST, a błąd ma różną strukturę w zależności od endpointa — integracja staje się obroną przed własnym API.
Format sukcesu w odpowiedzi API
Daty powinny być zawsze w formacie ISO 8601 (2025-01-15T10:30:00Z) — łatwe do parsowania w każdym języku, jednoznaczne co do strefy czasowej.
Format błędu w REST API zgodny z RFC 9457
Standard RFC 9457 (Problem Details for HTTP APIs) definiuje jednolity format dla błędów API. Zamiast wymyślać własną strukturę, warto trzymać się tego standardu:
Pola type (URI identyfikujące klasę błędu), title (czytelna dla człowieka nazwa klasy), status (kod HTTP) i detail (szczegółowy opis) tworzą maszynoczytelną i jednocześnie czytelną dla dewelopera strukturę. instance wskazuje konkretne wystąpienie błędu, co ułatwia debugowanie i korelację z logami.
Nawet jeśli nie chcesz pełnego RFC 9457, trzymaj się jednej zasady: spójny format błędów przez całe API. Mieszanie { "error": "..." }, { "message": "..." } i { "errors": [...] } w różnych endpointach to klasyczny dług techniczny.
Kolekcja z metadanymi i paginacją
Wrapper data + meta jest przydatny, bo pozwala rozszerzyć odpowiedź o metadane bez breaking change — wystarczy dodać nowe pole do meta.
Filtrowanie, sortowanie i paginacja w REST API
Filtrowanie przez query parameters
Sortowanie wyników w REST API
Alternatywna konwencja:
Paginacja offset i cursor w REST API
Offset-based:
Cursor-based (lepsze dla dużych zbiorów):
Offset pagination jest prostsza do implementacji i wygodna przy panelach admina, gdzie użytkownik chce przejść na konkretną stronę wyników. Ma jednak wadę przy dużych, zmieniających się zbiorach: jeśli ktoś doda rekord między requestami, page 2 może zawierać duplikaty z page 1 albo pominąć rekord.
Cursor pagination rozwiązuje ten problem — każda strona zaczyna od konkretnego punktu w zbiorze (zwykle ID lub timestamp ostatniego rekordu), niezależnie od tego, co się zdarzyło w zbiorze między requestami. To właściwy wybór dla nieskończonego przewijania, real-time feedów i API z wysoką częstotliwością zmian danych.
Odpowiedź z paginacją:
Wybór pól, czyli sparse fieldsets
To wygodne, ale warto whitelistować pola po stronie serwera, żeby klient nie mógł wyciągać wszystkiego bez kontroli.
Include i eager loading w odpowiedzi API
Tak samo jak przy fields, warto posiadać limit dopuszczalnych relacji, żeby nie zrobić przypadkiem własnego "mini-GraphQL bez guardrailów".
Wersjonowanie REST API
Wersjonowanie API w URL
Wersjonowanie API w nagłówku HTTP
Wersjonowanie API w query parameter
Rekomendacja: Wersjonowanie w URL jest najprostsze i najbardziej widoczne, ale nie każde API musi zaczynać od /v1, dlatego czasami wystarczy po prostu dobra kompatybilność wstecz i przemyślane polityki zmian.
Autoryzacja w REST API
Bearer Token i JWT w REST API
API Key w REST API
Odpowiedzi API przy autoryzacji
Kiedy wersjonować REST API i jak robić to bez chaosu
Wersjonowanie API to jeden z tych tematów, które boli dopiero wtedy, gdy jest za późno. Jeśli masz publiczne API i wprowadzisz breaking change bez wersjonowania, psujesz wszystkich konsumentów naraz.
Breaking change to m.in.:
- zmiana nazwy pola lub usunięcie pola z odpowiedzi
- zmiana typu pola (string → number)
- zmiana znaczenia kodu statusu dla danego endpointa
- usunięcie endpointa
Non-breaking change to m.in.:
- dodanie nowego opcjonalnego pola w odpowiedzi
- dodanie nowego endpointa
- dodanie opcjonalnego query parametru
Wersjonowanie w URL (/api/v1/users) jest najpopularniejsze i najprostsze — widać je w URL, łatwo testować, logować i routować na różne implementacje. Wadą jest to, że trzeba utrzymywać kilka wersji jednocześnie. Praktyczna zasada: zacznij od /v1 przy pierwszym publicznym release'ie, nawet jeśli nie masz jeszcze /v2 w planach. Dodanie wersjonowania do bezwersyjnego API jest samo w sobie breaking change.
Rate limiting w REST API
API powinno informować o limitach:
Po przekroczeniu:
Idempotency key dla wrażliwych POST-ów
Tworzenie zasobów po POST nie jest idempotentne, przy płatnościach, webhookach albo retriable requestach warto dodać klucz idempotencyjny:
Serwer powinien rozpoznać ponowienie tego samego żądania i nie wykonać operacji drugi raz.
HATEOAS w REST API jako opcja, nie obowiązek
Hypermedia As The Engine Of Application State, czyli linki do powiązanych zasobów:
W praktyce jest to rzadko w pełni implementowane, ale warto chociaż poznać koncept.
Przykład REST API dla bloga
Endpointy API bloga
Przykładowe żądania do REST API
Chcesz zbudować własne API? Przejdź do Backend dla frontendowca: serwer, bazy danych i API.
