API je rozhraní, přes které tvůj backend nabízí svoje funkce okolnímu světu — frontendu, mobilní aplikaci, jiné službě. Je to smlouva: „když mi pošleš tenhle požadavek, udělám tohle a vrátím ti tamto." A jakmile tu smlouvu někdo začne používat, nemůžeš ji jen tak změnit, aniž bys mu rozbil aplikaci. Dobrý návrh API je hlavně o ohleduplnosti k tomu, kdo ho bude volat.
Pár pojmů na úvod
- API (Application Programming Interface) = sada „vstupních bodů" tvého backendu, které může někdo zavolat. Ne pro člověka jako web, ale pro jiný program.
- Endpoint = jeden konkrétní vstupní bod = kombinace cesty a metody, např.
GET /coursesneboPOST /orders. - Zdroj (resource) = věc, se kterou API pracuje (kurz, objednávka, uživatel).
- Payload / tělo (body) = data, která v requestu posíláš nebo v response dostáváš (obvykle JSON).
- Klient (konzument API) = ten, kdo tvoje API volá. Může to být tvůj vlastní frontend i cizí appka.
Vycházíme z Foundations — HTTP metody a status kódy už znáš, teď je použijeme k návrhu rozumného API.
Styly API — tři přístupy
Existují tři hlavní způsoby, jak API postavit. Nejsou to soupeři, každý se hodí jinam:
| Styl | Jak se na to dívat | Kdy ho zvolit |
|---|---|---|
| REST | Pracuješ se zdroji přes HTTP metody | Default. Veřejná API, běžné CRUD |
| gRPC | Voláš funkci na vzdálené službě | Komunikace mezi tvými vlastními službami, kde jde o rychlost |
| GraphQL | Klient si řekne přesně o data, která chce | Když klient (často mobil) potřebuje pokaždé něco jiného |
Jako junior začni s REST — je nejrozšířenější a všechno ostatní pochopíš snáz, až ho budeš mít v ruce. gRPC a GraphQL si necháš, až narazíš na konkrétní potřebu.
REST pořádně
Hlavní myšlenka REST: zdroj je podstatné jméno, akci určuje HTTP metoda. Do URL tedy nepatří
slovesa — co s daným zdrojem děláš, říká už metoda (GET, POST…).
✅ GET /courses vrať seznam kurzů
✅ GET /courses/42 vrať detail kurzu 42
✅ POST /courses vytvoř kurz
✅ PUT /courses/42 nahraď celý kurz 42
✅ PATCH /courses/42 uprav část kurzu 42
✅ DELETE /courses/42 smaž kurz 42
✅ GET /courses/42/lessons lekce patřící ke kurzu 42
❌ GET /getCourses (sloveso v URL — to dělá GET)
❌ POST /createCourse (sloveso v URL — to dělá POST)
❌ POST /courses/42/delete (na mazání je DELETE)
Připomenutí sémantiky metod (z Foundations): GET jen čte (nic nemění, jde cachovat), PUT a DELETE jsou idempotentní (zopakování nic nezkazí), POST idempotentní není — a právě proto existují idempotency keys ↓.
Idempotency keys — jak bezpečně opakovat POST
Připomeň si pojem idempotentní z Foundations: operace, kterou můžeš provést
vícekrát se stejným výsledkem. Problém je, že POST (vytvoř něco) idempotentní není.
Představ situaci: klient pošle POST /payments, ale spojení vyprší (timeout) dřív, než dorazí
odpověď. Klient neví, jestli platba proběhla. Co udělá? Zkusí to znovu (retry) — a když ta první
ve skutečnosti prošla, právě jsi zákazníkovi naúčtoval dvakrát.
Řešení: klient ke každému takovému requestu přiloží unikátní klíč (Idempotency-Key). Server si
podle něj pozná, že už ten požadavek viděl:
Server si páruje klíč → výsledek (třeba v Redisu nebo databázi, na omezenou dobu). Podrobně, včetně záludností, v kapitole Patterny → Idempotency. Tohle je naprostý základ, který by měl umět každý backend engineer.
Verzování — jak měnit API a nerozbít staré klienty
Tvoje API se bude vyvíjet, ale aplikace, které ho už používají, nesmí ze dne na den přestat fungovat.
- Přes URL:
/v1/courses,/v2/courses— nejjasnější a nejčastější způsob. - Přes hlavičku: verzi pošleš v hlavičce requestu — čistší URL, ale hůř vidět.
- Pravidlo: přidat nové (volitelné) pole je neškodné. Odebrat nebo přejmenovat pole, nebo změnit jeho typ, je breaking change (rozbije klienty) → dělej to v nové verzi.
⚠️ Nová major verze není zadarmo — znamená udržovat dvě implementace souběžně. Proto se s ní šetří: většina změn jde udělat zpětně kompatibilně (aditivně), bez nové verze. A když starou verzi rušíš, udělej to řízeně: ohlas to dopředu (hlavičky
Deprecation/Sunset) a dej klientům čas přejít. Neverzuj při každé změně.
Pagination — nikdy nevracej „všechno"
Kdyby GET /orders vrátilo všech 5 milionů objednávek, zabiješ tím databázi i klienta. Proto se
výsledky vrací po stránkách (pagination). Dva způsoby:
| Typ | Jak se volá | Výhody / nevýhody |
|---|---|---|
| Offset | ?limit=20&offset=40 (přeskoč 40, dej 20) | Jednoduchý, umí skočit na stránku N / pomalý na velkých datech a nestabilní (když mezitím někdo přidá řádek, stránky se posunou) |
| Cursor | ?limit=20&cursor=abc (dej 20 za bodem abc) | Stabilní a rychlý i na velkých datech / neumí skočit rovnou na stránku N |
Pro nekonečný scroll a velké tabulky používej cursor. Offset si nech na malé, neměnné seznamy.
Error contract — jednotný tvar chyb
Klient musí umět chybu zpracovat strojově, ne jen přečíst. Proto vracej chyby pořád ve stejném tvaru napříč celým API:
{
"success": false,
"error": {
"code": "VALIDATION_FAILED",
"message": "Vstup neprošel validací",
"details": [{ "field": "email", "issue": "invalid_format" }]
}
}
codeje stabilní řetězec pro stroj (kód na něj může reagovat),messageje text pro člověka. Klientovi nikdy neposílej syrový technický výpis chyby (stack trace) — je to únik informací a bezpečnostní riziko.- Vracej k tomu správný HTTP status (4xx = chyba klienta, 5xx = chyba serveru).
- Pole
detailspatří k validačním chybám (422— které konkrétní pole je špatně). Business konflikt (409, např. „kurz je plný") nemá konkrétní vadné pole — vrať jencodeamessage(třebacode: "COURSE_FULL"), žádnédetailsnecpi.
Další věci, co dělají API dobrým
- Konzistentní pojmenování — vyber si
snake_casenebocamelCasea drž ho všude stejně. - Filtrování a řazení přes query parametry:
?status=active&sort=-createdAt. - Rate limiting — omez počet requestů na klienta, ať ti někdo API nezahltí; přes kód
429(Security). - Dokumentace — popiš API standardem OpenAPI/Swagger, ideálně generovaným z kódu, ať nezestárne.
- Bezpečnost — každou operaci, co mění data, ověř (kdo a jestli smí), vstup validuj na serveru a nikdy nevěř tomu, co přijde od klienta (viz Security).
Bulk operace, dlouhé operace a API gateway
Tři situace, na které REST „základ" nestačí a je dobré vědět, jak na ně:
- Bulk operace (hromadné). Potřebuješ vytvořit/upravit 100 položek? Posílat 100 requestů je pomalé. Nabídni endpoint, který vezme pole položek najednou. Pak ale řeš částečný úspěch — co když projde 98 a 2 selžou? Vrať výsledek pro každou položku zvlášť, ne jen „OK / chyba".
- Dlouhé operace (async API). Když operace trvá dlouho (vygenerovat velký report), nenech klienta
čekat na jednu odpověď. Vrať hned
202 Accepteds odkazem „stav úlohy je tady", úlohu zpracuj na pozadí a klient si stav buď polluje (občas se zeptá), nebo mu po dokončení pošleš webhook. - API gateway. Když máš víc služeb, postavíš před ně jednu bránu, která řeší společné věci na jednom místě: ověření, rate limiting, směrování requestů na správnou službu. Klient mluví s bránou, ne přímo se službami. (Varianta BFF = Backend For Frontend: brána ušitá na míru konkrétnímu klientovi, třeba mobilu.)
Failure modes — kde se to nejčastěji pokazí
- Chybějící idempotency → duplicitní platby/objednávky, když klient po timeoutu zopakuje request.
- Breaking change bez nové verze → rozbiješ produkční klienty bez varování.
- Offset pagination na milionech řádků → pomalé a přeskakující/duplikující záznamy.
- Únik interních detailů v chybách → posíláš klientovi stack trace nebo SQL = bezpečnostní díra.
🛠️ Cvičení
- Navrhni REST API. Pro „kurzy a jejich lekce" napiš endpointy: výpis a detail kurzu, vytvoření a úprava kurzu, výpis lekcí kurzu a zápis studenta. Bez sloves v URL, se správnými metodami a status kódy.
- Idempotentní POST. Naskicuj v pseudokódu handler
POST /paymentssIdempotency-Key: kde klíč ukládáš, jak poznáš opakovaný request a jak vrátíš uloženou odpověď. - Cursor vs offset. Pro nekonečný scroll feedu napiš, jaké query parametry použiješ, a vysvětli, proč offset na milionu řádků začne přeskakovat nebo duplikovat záznamy.
- Error contract. Navrhni jednotný tvar chyby a přiřaď k němu 4 situace i s HTTP kódy: neprošla validace, nepřihlášen, nemá právo, konflikt (např. plný kurz).
- Breaking change? U každé změny rozhodni, jestli je breaking (a tedy potřebuje novou verzi): přidání volitelného pole, přejmenování pole, zúžení typu, nový povinný parametr, změna formátu data.
Náčrt řešení — rozbal, až si cvičení zkusíš sám
- Navrhni REST API —
GET /courses,GET /courses/42,POST /courses(201),PUT/PATCH /courses/42,GET /courses/42/lessons, zápis studenta jakoPOST /courses/42/enrollments. Pointa: žádná slovesa v URL (zdroj = podstatné jméno, akci určuje metoda); past je dělatPOST /courses/42/enrollmísto zápisu nového zdroje. - Idempotentní POST — uložíš dvojici
klíč → výsledek(Redis/DB), na začátku ověříš, jestli klíč už existuje: pokud ano, vrátíš uloženou odpověď a neprovedeš znovu. Na co pozor: klíč musíš uložit atomicky se samotnou operací, jinak vznikne okno, kdy platba projde, ale klíč ne → duplicita. - Cursor vs offset — pro feed
?limit=20&cursor=abc(ukazatel na poslední položku). Pointa: offset na milionu řádků přeskakuje/duplikuje, protože když mezi requesty někdo přidá řádek, celé okno se posune; cursor se váže na konkrétní bod, takže je stabilní (jen neumí skočit na stránku N). - Error contract — jednotný tvar
{ success, error: { code, message, details? } }: neprošlá validace422(+detailspo polích), nepřihlášen401, nemá právo403, plný kurz409(jencode, např.COURSE_FULL, bezdetails). Pozor:detailspatří jen k validaci; do business konfliktu je necpi a klientovi nikdy neposílej stack trace. - Breaking change? — breaking jsou: přejmenování pole, zúžení typu, nový povinný parametr a změna formátu data (rozbijí klienty → nová verze); přidání volitelného pole je bezpečné. Pointa: aditivní změny dělej zpětně kompatibilně, novou major verzi si nech jen na ty breaking.
🧠 Otázky & odpovědi
Proč POST není idempotentní a jak to vyřešíš pro platby?
Zopakování POSTu vytvoří nový záznam nebo akci — retry po timeoutu tak může znamenat dvě platby. Řešení je idempotency key: klient pošle s requestem unikátní klíč a server si páruje klíč → výsledek. Když dorazí stejný klíč podruhé, operaci neprovede znovu, jen vrátí uloženou odpověď. Klíč se musí uložit atomicky se samotnou operací, jinak vznikne okno, kdy operace proběhne, ale klíč ne → duplicita.
Kdy použít cursor a kdy offset pagination?
Offset (limit/offset) je fajn na malé, neměnné seznamy a když chceš umět skočit na konkrétní
stránku — ale na velkých datech je pomalý a nestabilní (když mezi requesty někdo přidá řádek, okno se
posune a dostaneš duplikáty nebo přeskočíš). Cursor (ukazatel na poslední položku) je stabilní a
rychlý i na velkých a měnících se datech a hodí se na nekonečný scroll; neumí jen skočit na stránku N.
Co je breaking change a jak se mu bránit?
Změna, která rozbije existující klienty: odebrání nebo přejmenování pole, zúžení typu, nový povinný
parametr, změna významu. Přidat volitelné pole je naopak bezpečné. Breaking změnu zaveď v nové
verzi (/v2) a starou nech běžet, dokud klienti nepřejdou. Zásada: buď tolerantní k tomu, co
přijímáš, a opatrný v tom, co měníš ve výstupu.
Jak má vypadat dobrá chyba z API?
Vždy stejný tvar napříč API: strojově čitelný stabilní code (např. INSUFFICIENT_STOCK),
lidsky čitelný message a volitelně details pro chyby po jednotlivých polích. K tomu správný HTTP
status (4xx chyba klienta, 5xx chyba serveru). Klientovi nikdy neposílej stack trace ani SQL — je to
únik informací, který pomáhá útočníkovi.
Proč začít s REST a kdy teprve gRPC nebo GraphQL?
REST je univerzální, snadno se cachuje a rozumí mu každý → ideální default i pro učení. gRPC (rychlý, typovaný) se hodí na komunikaci mezi tvými vlastními službami, kde jde o nízkou latenci. GraphQL zvol, když klient (typicky mobil) potřebuje pokaždé jinou sadu dat a chceš se vyhnout posílání zbytečností — za cenu složitějšího serveru a obtížnějšího cachování.
Jak navrhnout API pro operaci, která trvá dlouho?
K čemu je API gateway?
Je to jedna brána před tvými službami, která na jednom místě řeší společné věci: ověření, rate limiting a směrování requestu na správnou službu. Klient mluví s bránou, ne přímo se službami. Varianta BFF (Backend For Frontend) je brána ušitá na míru konkrétnímu klientovi (např. mobilu), aby dostával přesně ta data v takovém tvaru, jaký potřebuje.
