JavaScript na serveru: REST API

Další dva díly seriálu o Node.js budou věnovány tvorbě REST API. V dnešním díle se podrobněji podíváme na doporučené postupy pro tvorbu API. Příště popsanou teorii uvedeme do života.
Seriál: Node.js - s JavaScriptem na serveru (13 dílů)
- JavaScript na serveru: Začínáme s Node.js 23. 11. 2010
- JavaScript na serveru: Patří budoucnost Node.js? 21. 9. 2012
- JavaScript na serveru: Architektura a první Hello World 5. 10. 2012
- JavaScript na serveru: moduly a npm 12. 10. 2012
- JavaScript na serveru: začínáme programovat e-shop 19. 10. 2012
- JavaScript na serveru: MongoDB, Mongoose a AngularJS 26. 10. 2012
- JavaScript na serveru: Testování a kontinuální integrace 2. 11. 2012
- JavaScript na serveru: REST API 9. 11. 2012
- JavaScript na serveru: implementace REST API 16. 11. 2012
- JavaScript na serveru: nástroje a dokumentace 23. 11. 2012
- Začínáme s AngularJS 30. 11. 2012
- AngularJS direktivy a testování 7. 12. 2012
- JavaScript na serveru: CoffeeScript a šablonovací systémy 14. 12. 2012
O REST na Zdrojáku již jeden článek vyšel, takže je-li pro vás tento pojem neznámý, určitě si přečtěte článek od M. Malého REST: Architektura pro webové API.
Ačkoliv REST není přímo s Node.js nijak spjat, myslím si, že je dobré věnovat mu trochu více prostoru, aby bylo v pozdějších dílech seriálu čtenáři hned jasné, proč je třeba vrácen právě daný HTTP kód a ne jiný. Kromě toho je REST API jedna z oblastí, pro kterou se Node.js používá velmi často.
V tomto článku se nebudu věnovat autorizaci a autentizaci ani kešování obsahu. Obě témata si zaslouží samostatný díl a budeme se jim věnovat v pozdějších dílech seriálu.
Proč REST API
Pokud budu uvažovat náš vyvíjený e-shop, může se REST API využít při mnoha příležitostech:
- Webové rozhraní pro nákup v e-shopu. V našem případě řešeno přes HTML5 a framework AngularJS.
- Administrační rozhraní. Velmi často provozuje jeden zákazník více e-shopů, takže může požadovat jedno administrační rozhraní pro všechny e-shopy.
- Import dat. Téměř všechny e-shopy importují produkty z nějakých externích systémů. Buď se tento systém sám na náš e-shop napojí, nebo vytvoříme „můstek“, který data z daného systému překlopí přes API do našeho systému.
- Export dat. Každý e-shop potřebuje data někam exportovat, např. do účetního systému.
- Různé další projekty, které potřebují s e-shopem komunikovat. Žijeme v době startupů, kdy i nejjednodušší problém řeší mnoho zajímavých projektů. Ty však potřebují nějak automatizovaně s naším e-shopem komunikovat.
- Verze pro smartphony a tablety. Můžete chtít speciální mobilní verzi pro Android či iPhone, která ale potřebuje odněkud získávat data. Máme-li např. e-shop pro prodej jízdenek, budeme určitě chtít i mobilní verzi.
- Mnoho dalších možností, affiliate systémy nebo třeba reklamní aplikace s katalogem mobilních telefonů. A data do aplikace budeme importovat z našeho e-shopu s telefony (samozřejmě v detailu telefonu nezapomeneme na tlačítko „Koupit“).
Nejlepší postupy pro REST
Dále popisuji základní doporučení návrhu relevantní k vyvíjeném e-shopu. Máte-li zájem o hlubší prozkoumání problematiky, pak lze doporučit knihu REST API Design Rulebook nebo REST tutoriál.
Bohužel každá kniha věnující se REST se v různých oblastech do značné míry rozchází. Podobně je tomu v případě API známých serverů typu Facebook či Twitter. Následující řádky obsahují mix doporučení a postupů, které považuji za nejlepší a nejjednodušší.
HTTP metody
Správné použití HTTP metod je popsáno v odkazovaném článku, zde jen zopakuji, že metoda GET se používá pouze pro získávání dat, metoda POST se nejčastěji používá pro vkládání, PUT pro editaci a DELETE pro mazání.
HTTP kódy
Protokol HTTP obsahuje celou řadu stavových kódů, které se používají pro specifické události.
200 OK
Nejpoužívanější HTTP kód. Je vrácen v případě správného zpracování HTTP požadavku. HTTP odpověď by měla obsahovat další data. Pokud chceme pouze oznámit, že požadavek byl úspěšně zpracován a žádná další data odesílat nechceme, použije se HTTP kód 204. Kód 200 nikdy nesmí být navrácen v případě, že došlo k chybě (často se tak však mylně používá).
201 Created
Používá se při vytvoření nového zdroje. Tedy když úspěšně vytvoříme nový produkt přes POST/PUT /products, vracíme kód 201 a odkaz na nově vytvořený zdroj v hlavičce Location.
202 Accepted
Používá se tehdy, začne-li zpracování nějaké dlouhotrvající operace. Místo toho, abychom čekali na dokončení operace (může trvat velmi dlouho), vrátíme HTTP kód 202. Tím říkáme, že požadavek je validní a jeho zpracování bylo úspěšně zahájeno. Neříká ale nic o tom, jak operace dopadla.
204 No Content
Používá se podobně jako HTTP kód 200, ale nevrací žádný obsah. Používá se např. při mazání zdroje. Když smažeme nějaký produkt, vrátíme kód 204, kterým říkáme, že byl úspěšně z databáze odstraněn.
301 Moved Permanently
Říká, že zdroj byl přesunut na novou adresu. Rozhodneme-li se změnit URL produktu v e-shopu, vrátíme tento kód a hlavičku Location s novou adresou. Prohlížeč by měl po obdržení kódu 301 automaticky aktualizovat všechny reference, takže na původní URL už dotaz nepůjde.
303 See Other
Říká, že požadavek byl zpracován, ale nevrací informace o výsledku zpracování, pouze v hlavičce Location uvádí adresu, kde je možné podrobnější informace o zpracování získat. Hodí se to např. u mobilních aplikací, kdy je potřeba hlídat každý přenesený byte. Nebo pokaždé, když je potřeba přesměrovat z webového formuláře (po POSTu) na nějakou cílovou stránku, třeba po přihlášení.
304 Not Modified
Souvisí s kešováním obsahu. Server tímto kódem říká, že prohlížeč má u sebe aktuální verzi dokumentu a není potřeba, aby stahoval stejnou verzi. Odpověď tedy neobsahuje tělo.
307 Temporary Redirect
Říká, že server aktuálně nemůže zpracovat daný požadavek a místo toho v hlavičce Location posílá adresu, na které má být požadavek o zpracování znovu zaslán.
400 Bad Request
Používá se pro oznámení chyby, na který nelze použít jiný 4×x status.
401 Unauthorized
Neautorizovaný přístup. Uživatel zadal buď chybné přihlašovací údaje, nebo nezadal přihlašovací údaje vůbec.
403 Forbidden
Uživatel požaduje informace o zdroji, ke kterému nemá přístup.
404 Not Found
Uživatel se dotazuje na URL, která neexistuje a nikdy neexistovala. Kód 404 se používá i v situaci, kdy samotné prozrazení existence obsahu na URL má nepříznivý vliv na bezpečnost. Proto je v některých případech legitimní odpovědět místo kódu 401 či 403 tímto kódem.
406 Not Acceptable
Uživatel požaduje vrátit data ve formátu, který server nedokáže obsloužit. Souvisí s hlavičkou Accept, ve které může např. uživatel říct, že chce data ve formátu XML, server je však dokáže odeslat jen ve formátu JSON. Tehdy vrátí kód 406.
409 Conflict
Používá se tehdy, pokud by zpracování požadavku uvedlo aplikaci do nekonzistentního stavu. Chce-li uživatel smazat dokument, na kterém jsou závislé jiné dokumenty, vrátíme tento kód. Může se hodit třeba při požadavku na smazání produktu, který právě někdo nakupuje.
410 Gone
Říkáme, že byl dokument trvale smazán. Pokud víme, že na této adrese něco existovalo, a už to zde není, vracíme 410, nikoliv 404. Na dané adrese by se již nikdy nemělo nic nacházet.
415 Unsupported Media Type
Podobný status jako HTTP 406, ovšem zde říkáme, že uživatel zaslal data ve formátu, který nejsme schopni zpracovat. Např. nám uživatel zaslal data v XML, server ale umí zpracovat jen JSON.
500 Internal Server Error
Chyba je na straně serveru a nesouvisí přímo s obsahem požadavku uživatele. Např. dojde k vyhození výjimky, která nebyla zachycena a zpracována.
HTTP kódů je o něco více, jejich kompletní přehled najdete na české Wikipedii.
Struktura URL
URL má jednoduchý a intuitivní formát a může odkazovat na zdroj, který pracuje buď s celou kolekcí, nebo s konkrétním elementem (dokumentem) kolekce.
Odkaz na celou kolekci produktů může vypadat takto:
example.com/products
Odkaz na konkrétní element produktu s ID 123 takto:
example.com/products/123
Chceme-li se odkázat na dokument, který závisí na jiném dokumentu a nemůže existovat zvlášť, přidáme do cesty i rodičovský dokument. Např. varianty produktů nemohou existovat samostatně, takže na variantu s ID 456 produktu ID 123 se můžeme odkázat takto:
example.com/products/123/variants/456
Kromě práce se zdroji můžeme chtít provádět na kolekci nebo elementu nějakou akci. Např. URL pro vygenerování faktury pro objednávku číslo 123 může mít adresu:
example.com/orders/123/generate
Akce se bude volat metodou HTTP POST, protože žádná akce GET nesmí data měnit.
Názvosloví
Pro názvy kolekcí se používají vždy podstatná jména v množném čísle a nejlépe v anglickém jazyce. Pro názvy akcí na kolekcích či dokumentech se naopak používají slovesa.
Pro oddělení slov v názvech kolekcí či dokumentů se používají pomlčky. Pro názvy atributů se vždy používá tzv. “cammelCase”, tedy např. dateCreated. Podtržítka se nepoužívají nikdy.
Formát komunikace
Nejčastěji se dnes komunikuje přes JSON, který se doporučuje používat jako výchozí formát. Vedle toho se velmi často používá XML. Pokud chce uživatel vrátit odpověď v konkrétním formátu, zasílá se HTTP hlavička Accept.
Filtrování
Pro filtrování výsledků je nejjednodušší přidat požadované filtry do URL, tedy chceme-li např. vrátit produkty s cenou 1000, mohlo by URL to vypadat takto:
example.com/products?price=1000
Stránkování
Pro stránkování lze přidat do URL parametr limit (říká, kolik záznamů se má vrátit) a offset (od kterého záznamu). Tedy chceme-li vrátit 25 produktů od prvního záznamu, můžeme použit tento dotaz:
example.com/products?limit=25&offset=0
Zde je potřeba upozornit, že je vždy nutné validovat požadovaný rozsah a nastavit maximální rozsah, který můžeme vrátit.
Kromě toho je možné pro stránkování použít HTTP hlavičku Range a v odpovědi Content-Range.
Řazení
Pro řazení se použije v URL parametr sort, který obsahuje pole, podle kterých se mají dokumenty seřadit. Pokud chceme použít opačné pořadí, před název pole se vloží znak “-”.
Chcete-li tedy vrátit všechny produkty podle jejich ceny (price) a dále v opačném pořadí podle názvu (name), pak dotaz může vypadat takto:
example.com/products?sort=price,-name
Pole
Chceme-li vrátit jen určitá pole (sloupce), použije se parametr fields. Chceme-li tedy vrátit název produktu, perex a cenu, mohl by dotaz vypadat takto:
example.com/products?fields=name,perex,price
Verzování API
API je dobré hned od začátku verzovat a nejčastěji se používá prefix v + číslo verze. Takže např. kolekce s produkty bude dostupná na adrese:
example.com/v1/products
Umístění API
Nejčastěji se umisťuje na samostatnou subdoménu api, tedy např. api.example.com. V případě naší aplikace pro zjednodušení nebudeme vytvářet samostatnou subdoménu a API bude dostupné přes prefix /api, tedy produkty získáme na adrese:
example.com/api/v1/products
Dokumentace API
Dokumentace k API se doporučuje umísťovat na samostatnou subdoménu developers: developers.example.com.
Kromě toho je dobré přidat přesměrování z adres dev.example.com a developer.example.com.
Zpracování chyb
Nastane-li chyba, v HTTP odpovědi zasíláme vždy parametr message s popisem chyby. Volitelně pak parametr code (interní číslo chyby) nebo type (pojmenování chyby) a odkaz na podrobnější informace v parametru moreInfo. Tedy zadá-li např. uživatel v dotazu, že chce vrátit pole “perex”, které však nevracíme, vrátíme HTTP kód 400 s tímto popisem chyby:
{ code: 1234, message: "Požadované pole 'perex' neexistuje.", moreInfo: "http://developers.example.com/errors/1234" }
Co dále
V příštím díle dnes zmíněnou teorii uvedeme do praxe a upravíme e-shop tak, aby zmíněná pravidla plně respektoval.
Na tvorbě tohoto článku se svými připomínkami podílel také Pavel Lang (github). Díky!
Jde o to, že by s PUT by nemělo spojeno moc magie, prostě vytvoří nebo nahradí obsah tak jak je.
POST oproti tomu může magicky působit, protože může k resource přistupovat inkrementálně, přidávat a odebírat tagy a pod.
Toto je přibližný a doplněný překlad z stackoverflow.com. Resource nepřekládám… (dá se překládat jako zdroj/zdroje, ale je to zbytečně matoucí)
POST:
Používá se k úpravě nebo aktualizaci existujícího resource:
POST /otazky/<existujici-otazka> HTTP/1.1
Host: blahblah.cz
Ne však k vytvoření neexistujícího resource, toto je chybné použití:
Pokud resource na URL není ještě vytvořen, neměla by se POST metoda používat k vytvoření. Takový požadavek na server by měl být zodpovězen jako 404 Not Found.POST /otazky/<nova-otazka> HTTP/1.1
Host: blahblah.cz
Na druhou stranu stejný PUT požadavek na server by měla resource vytvořit a odpovědět kódem 201 Created (nebo 303)
Je ale možné udělat něco takového pro vytvoření resource pomocí POST:
Rozdíl je v tom, že v tomto případě není jméno resource uvedeno v URL. V tomto případě by měl server odpovědět URL na které se vytvořený resource nachází.POST /otazky HTTP/1.1
Host: blahblah.cz
PUT:
Používá se k:
– vytvoření, pokud resource na URL neexistuje, server odpoví 201 Created
– přepsání, úplnému nahrazení resource, pokud již existuje, server odpoví 202 Accepted
Pro vytvoření resource:
PUT /otazky/<nova-otazka> HTTP/1.1
Host: blahblah.cz
Pro přepsání existujícího resource:
PUT /questions/<existing_question> HTTP/1.1
Host: wahteverblahblah.com
Jakube, omlouvám se jestli jsem to nezachytil, když jsem článek revidoval, tak alespoň tady… :-)
Jenom ještě dodám, že RESTfull APIs nejsou exaktní věda :-) Prostě je potřeba mít trochu citu, alespoň stručnou dokumentaci a hlavně integrační testy!
Souhlasím, pouze bych opravil toto:
„PUT — přepsání, úplnému nahrazení resource, pokud již existuje, server odpoví 202 Accepted“ S tímto nesouhlasím, PUT by měl většinou odpovídat 200 OK, pokud server operaci dokončil před odesláním odpovědi klientovi.
Kód 202 Accepted se používá, pokud operace nebyla dokončena před odesláním odpovědi, a je jedno jestli to byl POST, PUT nebo DELETE. Kód 202 naznačuje klientovi, že poslal správně formulovaný požadavek, ale že operace není ještě hotova. Klient pak většinou používá periodický polling ke zjištění, zda už operace proběhla.
Díky za připomínku, zní to logicky. Jen by mě zajímalo jak probíhá následný polling. Klient pošle GET požadavek a dostane celý obsah, nebo může posílat HEADER, pokud ho zajímá pouze stavový kód?
Samozřejmě ne HEADER ale HEAD… nevím, měl asi jsem bug v idei…
Seriál se mi líbí, ale mám pocit, že zbytečně odbíhá od tématu. Pitvání různých HTTP kódů mi přijde zbytečné. Chci více nodejs, méně věcí kolem.
Jinak „sort“ je přesnější překládat jako řazení. Třídění je rozklad množiny do tříd.
Děkuji za reakci. Původně měl být článek delší a jeho druhá část měla být věnována úpravě aplikace podle popsané teorie + další informace o zajímavých Node.js modulech v oblasti REST, ale bohužel tento týden jsem měl mnohem méně času, a tak jsem se rozhodl článek rozdělit na dvě části. Předchozí tři díly zabraly každý minimálně 6 až 7 hodin a tento týden jsem seriálu tolik času věnovat nemohl.
Popsaná teorie mi přijde důležitá pro celý zbytek seriálu. Programoval jsem systém, na kterém dnes běží cca 100 e-shopů a téměř každý obsahoval požadavky na import a export dat do nějakého dalšího systému. Viděl jsem jich desítky a s čistým svědomím můžu říct, že všechny do jednoho měly řadu chyb v návrhu, kvůli kterém byla implementace napojení vždy část, která zabrala nejvíce času.
Seriál je nyní ve fázi vytváření prostředí pro vývoj v Node.js (hosting, databáze, testovací prostředí atd.) a tato fáze je již téměř u konce. V dalších částech seriálu bude zaměření čistě na Node.js větší a budu rycheji postupovat v tvorbě vzorové aplikace, která díky tomu může být reálná a schopná produkčního nastavení místo aplikace, která je pouze lepší „Hello World“.
Překlad sort opravíme, děkuji.
Ja si zase myslim, ze je to pojato vhodne. Jen je skoda toho rozdeleni na 2 samostatne clanky. Priklanel bych se i k brzkemu popisu autorizace a autentifikace :) Serial se mi dost libi (to poznam tak, ze netrpelive cekam, az vyjde dalsi dil ;D), takze jen tak dal.
Dobrý den,
v článku zmiňujete, že se budete věnovat v dalších dílech autorizaci a autentizaci. Chtěl bych se zeptat, jaké moduly (knihovny) na autorizaci a autentizaci používáte (dříve jste se již zmínil o Passport a Everyauth)?
Děkuji.
Dobrý den,
omlouvám se za pozdní odpověď, Váš příspěvek jsem přehlédl. Jak píšete, Passport a Everyauth jsou nejpopulárnější moduly. Nejprve jsem používal Everyauth, později jsem ale přešel na Passport, protože do Everyauth se dost těžko vstupuje, potřebuje-li člověk udělat něco trochu jinak. Kromě toho Passport je lépe otestován (viz issues na Githubu u obou projektů). Kromě těchto modulů se často ještě zmiňuje mongoose-auth (https://github.com/bnoguchi/mongoose-auth), což je plugin pro Mongoose postavený na Everyauth, s ním ale zkušenosti nemám. Jinak modulů pro tuto oblast je v Node.js velké množství, jejich přehled lze nalézt na https://npmjs.org/browse/keyword/auth.
Ještě zde zmíním výborný český (nikoli počeštěný) startup na testování RESTfull APIs: apiary.io.
Určitě stojí za to vyzkoušet.
Bude o něm řeč příště: http://docs.zdrojak.apiary.io
Ačkoli naši čeští kluci odvedli skvělou práci, nemyslím si, že by se o jejich výtvor měl opírat nějaký další článek v seriálu. Paralelně o tom může napsat někdo jiný (že Adame :-) ), ale myslím, že už zevrubným popisem RESTfull API se seriál zaměřený primárně na node odebírá trochu jiným směrem než měl cílit a to je trochu škoda, ne pro teď, ale jako „návod“ někdy v budoucnu tím trochu trpí.
Jsou to dva odstavce na konci článku, to si myslím, že nevadí. A odstavce se vztahují tomu, co se vytváří, zmínka o nich do článku patří. Je těžké v českém seriálu o Node.js a REST API nezmínit Apiary, český startup pro REST API v Node.js:-)
Ok :-)
V současné době se s použitím REST architektury setkávám stále častěji a začínám jí i implementovat do svých aplikací.
Proto mi pomohl hezký seznam kódů používaných pro odpověď. Jak jsem zjistil, používal jsem některé špatně.
Zajímalo by mě také, zda pro zvolení formátu komunikace není jednoduší používat na konci URL příponu formátu „.json“ nebo „.xml“. Protože použití hlavičky s accept požadavkem se mi zdá, hlavně při GET dotazech, u kterých není potřeba autorizace, zbytečně složité.
Jinak moc děkuji za hezký článek, který teda toho neměl moc společného s nodeJS :-))