JavaScript na serveru: MongoDB, Mongoose a AngularJS

V dnešním díle budeme pokračovat s tvorbou API pro stránky. Seznámíme se s NoSQL databází MongoDB a modulem Mongoose, vytvoříme první skripty v klientském frameworku AngularJS od Google a seznámíme se s dalšími nástroji, které zpříjemňují vývoj aplikací.
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
Nálepky:
Šablony pro projekty
Existuje několik projektů, které shromažďují nejlepší praktiky pro vývoj aplikace nad konkrétními technologiemi. V angličtině se často označují pojmem boilerplate a vůbec nejznámějším zástupcem je HTML5 Boilerplate, což je výchozí šablona pro nové aplikace v HTML5 a CSS3. Najdete zde nejlepší postupy např. pro .htaccess nebo předpřipravený js kód, který ošetří situaci, kdy při ladění zapomenete v kódu odkaz na objekt console
, který v jiných prohlížečích může způsobit chybu. Existuje také zvláštní verze pro mobilní aplikace.
Obvykle nepoužijete vše, co HTML5 Boilerplate nabízí. Proto je dobré mít vlastní účet na GitHubu a celý projekt si “forknout”, tedy vytvořit vlastní kopii. Na GitHubu se to provádí velmi snadno kliknutím na tlačítko fork vpravo nahoře. Zde si pak šablonu upravíte podle sebe a každý nový projekt z ní odvodíte.
Na konci seriálu si vytvoříme vlastní šablonu pro nové projekty, která bude odvozená z vyvíjeného e-shopu.
CSS frameworky
Při tvorbě nového projektu je také dobré použít nějaký CSS framework, aby nebylo nutné při vývoji webu pracovat s čistým HTML (před implementací designu). Vůbec nejznámější CSS framework je Twitter Bootstrap, s velkým náskokem nejsledovanější projekt na GitHubu. Kromě Twitter Bootstrapu je rovněž populární Foundation Zurb. V průběhu seriálu budeme používat Twitter Bootstrap.
MongoDB a NoSQL databáze
Data naší aplikace budeme ukládat do MongoDB, která je jedna z nejpopulárnějších NoSQL databází. S MongoDB se komunikuje přes JavaScript (opravdu je to jeden jazyk na všechno) a data si databáze ukládá ve formátu BSON (Binary JSON).
Začít s MongoDB je mnohem snazší než začít s relační databází, kde je potřeba se naučit nejprve SQL. V SQL máme databáze, které dále obsahují několik tabulek s jasně danou strukturou, které pak obsahují libovolný počet záznamů. V MongoDB máme také databáze, které však obsahují kolekce, které dále obsahují libovolný počet dokumentů. Dokument je v tomto případě objekt ve formátu JSON.
Zatímco v SQL musíte vždy předem říct, jak přesně budou data v tabulce vypadat, v MongoDB tohle neplatí. Není potřeba předem vytvářet žádnou strukturu kolekce a dokonce není potřeba ani vytvářet danou kolekci. Ta se vytvoří automaticky při prvním vložení dokumentu. Pokud se zeptáte na data z kolekce, která ještě v databázi neexistuje, vrátí se vám prázdný počet výsledků, nikoliv chyba.
V SQL jsou data normalizována podle několika normálních forem a v tabulce jsou data vždy na jedné úrovni. V MongoDB mohou být data libovolně zanořena. Např. kolekce s produkty může obsahovat dokument produkt, který bude mít několik potomků (subdokumentů) variant.
V MongoDB neexistují JOINy jako v SQL, vše je směřováno k tomu, aby se data vždy získávala jednoduchým dotazem. Tedy např. parametry produktů budou včetně názvů uloženy v kolekci s produkty. Dochází tedy k duplikaci dat. Jednoduchý způsob získávání dat je zaplacen jejich složitější aktualizací. Budete-li tedy chtít změnit název parametru produktu, musíte dotazem projít všechny záznamy a název parametru změnit.
Chcete-li se podrobněji seznámit s MongoDB, pak můžete projít tutoriál v podobě interaktivního shellu.
Instalace
Instalace MongoDB je velmi jednoduchá. Pro Windows je dostupný klasický instalátor, instalace pro ostatní systémy je popsána v dokumentaci. Po instalaci je potřeba vytvořit složku, kam bude MongoDB ukládat svá data, defaultně používá cestu C:datadb.
Spuštění
MongoDB se spustí přes program mongod (nainstaloval se do C:mongodbbin). Při spuštění si mongod vytvoří zámek, aby nebylo možné spustit program dvakrát. Jako zámek si vytvoří soubor C:datadbmongod.lock. Pokud dojde k nestandardnímu vypnutí, tak zde soubor mongod.lock zůstane a je potřeba ho smazat, jinak nebude možné mongod spustit.
Spuštění může vypadat takto:
Vložení prvního záznamu
Vedle mongod je potřeba spustit ještě program mongo (ve stejné složce), přes který budeme s databází komunikovat. Následuje jednoduchá ukázka, jak vložit a získat nový dokument do databáze.
Příkaz use zdrojak
funguje stejně jako v SQL, měníme aktuálně používanou databázi. Dále jsme si vytvořili dokument product, který pak vložíme do kolekce products metodou insert()
. Stejně jako databáze se i nová kolekce vytvoří až v tomto okamžiku. Nakonec metodou find()
vracíme na nově vytvořené kolekci všechny dokumenty. Všimněte si, že byl vrácen i sloupec _id
. To je unikátní řetězec, který se vytváří automaticky při vložení.
MongoVUE a další nástroje
Pro pohodlnější práci s MongoDB je dobré stáhnout si nějaké grafické nástroje. Já mám nejraději nástroj MongoVUE. Je velmi vhodný pro začátečníky, protože ve spodní části se ukazují příkazy, které program zadal na databázi, takže se programátor naučí, jak s databází komunikovat. Pokud se vám MongoVUE nelíbí, můžete si vybírat z celé řady jiných nástrojů pro Admin UI.
Spojení s Heroku
Abychom mohli používat MongoDB i na Heroku, musíme si nejprve doplněk aktivovat zde: https://addons.heroku.com/mongohq. Nejnižší varianta je dostupná zdarma. Při aktivaci rozšíření po vás bude nejspíš chtít Heroku údaje o platební kartě pro ověření, že to s Heroku myslíte vážně (žádná částka se ale z účtu odečítat nebude). Následně se dostanete rozhraní pro práci s databází. V kolonce Database Users si můžete vytvořit nového uživatele, v kolonce Database Info jsou informace o přístupu k databázi.
Alternativy k Heroku
Nechcete-li zadávat Heroku údaje o své platební kartě, můžete využít některé ze služeb Database-as-a-Service, jako je např. MongoHQ či MongoLab, které nabízí základní verzi zdarma. Jak v případě Heroku, tak v případě ostatních služeb dostanete externí přístup k databázi a můžete aplikaci vyvíjenou lokálně napojit na vzdálenou databázi. Ze své vlastní zkušenosti můžu doporučit službu MongoLab, která nabízí zdarma úložiště pro 500 MB dat.
Mongoose
Mongoose je Node.js modul, který umožňuje jednodušší přístup k objektům v MongoDB. Nabízí podobnou funkcionalitu jako různé ORM frameworky, např. Doctrine či Zend_Db_Table pro PHP nebo Active Record pro Ruby On Rails.
Mongoose nabízí nástroje pro CRUD operace (vytváření, čtení, editaci a mazání) nebo třeba umožňuje provádět validaci před vložením dokumentu do kolekce (např. máte jistotu, že bude v databázi vždy e-mail nebo URL).
Podobně jako mnoho jiných projektů v Node.js i Mongoose nabízí databázi komunitních pluginů. Takže pokud např. chcete automaticky vkládat do databáze datum poslední změny, můžete využít plugin mongoose-timon. Na tomto příkladu je vidět, že pro každou drobnost (timon má jen 10 řádků kódu) už existuje otestovaný plugin, takže nemusíte programovat téměř nic, raději využít komunitní moduly a šetřit svůj čas na řešení specifičtějších problémů.
AngularJS
Pro klientskou část naší aplikace použijeme framework AngularJS od společnosti Google. Angularu budeme věnovat samostatný díl, v dnešním díle se mu věnovat nebudeme. Zatím si projděte zdrojové kódy na Githubu v adresáři public a pokud vám nevadí angličtina, tak velmi doporučuji špičkový tutoriál od tvůrců frameworku.
Pokračujeme v tvorbě API pro stránky
Pokud jste postupovali podle nastavení git repozitáře v předchozím díle, nový díl stáhnete příkazem git checkout -f dil6
. Nezapomeňte následně nainstalovat nové moduly příkazem npm install
.
app/models/Page.js
Modely budeme ukládat do adresáře app/models. Obsah souboru vypadá takto:
var mongoose = require('mongoose'); var Schema = new mongoose.Schema({ title: String , url: String , content: String }); Schema.statics.findOneByUrl = function(url, cb) { Model.findOne({url: url}, cb); }; var Model = module.exports = mongoose.model('Page', Schema);
Nejprve říkáme, jak bude vypadat schéma pro kolekci Page. Ačkoliv je MongoDB bezschémová databáze, při použití Mongoose je potřeba definovat alespoň nejnižší úroveň schéma kolekce. V našem případě pouze říkáme, že chceme, aby naše kolekce měla tři sloupce a všechny budou řetězce. U každého pole je možné určit více příznaků. Můžeme např. říct, že je dané pole povinné nebo určit výchozí hodnotu. Určení schéma má také výhodu v tom, že si Mongoose pohlídá, aby se do kolekce nedostala data (i pole), která mít v databázi nechceme.
V další části vytváříme statickou metodu findOneByUrl()
, což je pouze obal nad metodou Model.findOne()
, která vybere jeden záznam podle dané podmínky.
Poslední příkaz říká, že schéma platí pro model Page. Mongoose se bude na tomto modelu dotazovat na kolekci pages (automaticky vytváří množné číslo z názvu modelu). Příkaz module.exports
říká, že dostupné rozhraní modulu bude návratová hodnota příkazu mongoose.model()
.
app/controllers/PageController.js
var Page = require('../models/Page'); exports.index = function(req, res, next){ Page.find(function(err, docs) { if (err) return next(err); res.json(docs); }); }; exports.show = function(req, res, next){ res.send(req.page); }; exports.create = function(req, res, next){ var page = new Page(); page.title = req.body.title; page.url = req.body.title; //todo page.content = req.body.content; page.save(function(err, doc) { if (err) return next(err); res.json(doc); }); }; exports.update = function(req, res, next){ req.page.title = req.body.title; req.page.content = req.body.content; req.page.save(function(err, doc) { if (err) return next(err); res.json(doc); }); }; exports.destroy = function(req, res, next){ req.page.remove(function(err, doc) { if (err) return next(err); res.json(doc); }); };
V metodě index()
oproti minulému dílu vybíráme data z databáze pomocí metody Page.find()
, která vrací všechny záznamy v kolekci. Metoda je asynchronní, takže se jí přidává callback, který bude zavolán, jakmile budou výsledky získány z databáze.
Callback přijímá dva parametry, err
může obsahovat informace o chybě, docs
je seznam dokumentů získaných z databáze. V Node.js platí jednotná konvence, že první parametr callbacku u asynchronních metod je vždy informace o chybě (obvykle buď objekt Error
nebo null
, pokud chyba nenastala). V případě, že nastala chyba, bude zavolána funkce next()
, která předá chybu k dalšímu zpracování (o zpracování chyb v Node.js bude později samostatný díl).
V metodě show()
pouze předáváme dokument stránky v parametru req.page
do HTTP odpovědi. Jak vznikl parametr req.page
si povíme u popisu skriptu server.js.
Metody create()
i update()
jsou velmi podobné. Buď vytvářejí, nebo editují dokument Page v parametru req.page
. U metody create()
přiřazujeme do req.page.url
i titulek. To je pouze dočasné řešení, příští díl bude věnován testování a zde si ukážeme, jak vytvořit klasickou URL z názvu stránky.
V průběhu seriálu uděláme ještě do API několik změn, aby odpovídalo všeobecně doporučovaným pravidlům pro návrh API (HTTP kódy, návratové hodnoty, verzování ap.). I návrhu API bude věnován samostatný díl.
config.js
var express = require('express') , mongoose = require('mongoose'); exports.configure = function(app) { app.configure(function(){ app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(express.static(process.cwd() + '/public')); app.use(app.router); }); app.configure('development', function(){ app.set('db uri', 'mongodb://localhost/zdrojak'); }); app.configure('production', function(){ app.set('db uri', 'mongodb://user:pass@host:port/dbname'); }); } exports.connect = function(app) { mongoose.connect(app.get('db uri'), function(err) { if(err) console.log(err); }); }
Do souboru si budeme ukládat funkce související s konfigurací naší aplikace.
První funkce configure()
nastavuje konfiguraci pro vývojové i produkční prostředí. Funkce je volána třikrát, přičemž první volání neobsahuje název prostředí, což znamená, že platí pro všechna prostředí. Pro vývojové (výchozí, developement) a produkční (production) prostředí nastavujeme pouze jiné parametry pro připojení k databázi.
Volání app.configure('developement', cb)
je pouze zjednodušení zápisu
if ('developement' == app.get('env')) { cb()}
, přičemž metoda app.get()
vrací obsah systémové proměnné process.env.NODE_ENV
, a pokud není proměnná nastavena, vrací hodnotu „developement“.
Budete-li aplikaci posílat na Heroku, je potřeba změnit řetězec mongodb://user:pass@host:port/dbname
za skutečné parametry pro připojení k MongoDB. Kromě toho bude nutné po nahrání zdrojových kódů na Heroku nutné říct, kterou konfiguraci má Heroku brát jako hlavní. To lze provést tímto příkazem:
heroku config:add NODE_ENV=production
Nyní Heroku ví, že pro něj platí konfigurace v sekci production a pro vývoj se bude používat nastavení v sekci developement.
Ve výchozí sekci konfigurace říkáme, že naše aplikace má využívat několik middleware (rozebereme si je v dalších částech). Dnes je důležité znát middleware static()
, který říká, že cokoliv uvnitř adresáře public má být rovnou posláno do prohlížeče.
Druhá funkce connect()
provádí spojení s databází a v případě, že bude spojení neúspěšné, vypíše se do konzole chybové hlášení.
server.js
var express = require('express') , resource = require('express-resource') , mongoose = require('mongoose') , config = require('./config') , app = express(); //konfigurace a spojeni s databazi config.configure(app); config.connect(app); //controllery var PageController = require('./app/controllers/PageController'); //modely var Page = require('./app/models/Page'); //API stranek app.resource('pages', PageController, {base: '/api/', load: Page.findOneByUrl}); app.listen(process.env.PORT || 5000);
V souboru server.js načítáme několik modulů. Je důležité zmínit, že jsou vždy cachovány, takže např. soubor app/model/Page.js se načte vždy pouze jednou, i když ho lze používat i v jiných modulech. Načítání souborů funguje tedy podobně jako v PHP funkce require_once()
.
Důležitá změna je u definice API stránek. Třetí parametr nyní obsahuje konfigurační objekt pro dané API. Parametr base
udává prefix v URL, takže např. všechny stránky nyní budou k dispozici na URL /api/pages.
Parametr load
umožňuje autoloading. Říká, že pro určité metody (v našem případě show()
, update()
a destroy()
) se mají načíst data pro konkrétní dokument. K tomu právě použijeme dříve zmíněnou metodu findOneByUrl()
. Jako první parametr se jí předá URL konkrétní stránky. Podle URL se stránka načte a dokument je přiřazen do oné proměnné req.page
.
Naše API stránek ještě není dokončeno. Zatím nezpracováváme chyby a neřešíme ani situaci, kdy uživatel nevyplní všechny požadované položky. Vše bude podrobněji rozebráno v dalších dílech.
Demo aplikace po dnešním díle je možné vidět zde: http://young-lake-7194.herokuapp.com/. Můžete si všimnout, že pokud se dostanete jinam než na root (např. na seznam všech stránek /pages/) a provedete reload prohlížeče (F5), vyskočí na vás chyba, že aplikace danou URL nezná. To je (v tomto díle záměrně) způsobeno tím, že AngularJS používá HTML5 History API pro dynamickou změnu URL bez reloadu prohlížeče a na straně serveru nemáme ještě nastavena žádná pravidla mimo těch pro API. Na první pohled tedy není vůbec poznat, že jde o javascriptovou aplikaci. V dalším díle již toto chování upravíme tak, aby na aplikaci bylo možné odkazovat z jakékoliv úrovně.
Zdrojové kódy aktuální verze aplikace si můžete projít na githubu.
Co dále?
V příštím díle se budeme věnovat především testování. Seznámíme se s frameworkem Jasmine, řekneme si něco o behavioral-driven developementu (BDD) a napíšeme první jednotkové testy. A také trochu pokročíme s naším e-shopem.
Na tvorbě tohoto článku se svými připomínkami podílel také Pavel Lang (github). Díky!
Vďaka moc za tento seriál. Už dlhšie som sa chcel dostať k tomu, že si niečo podobné napíšem na skúšku a veľmi mi to uľahčuješ ;-)
Seriál je opravdu super, díky za něj a těším se na další díly.
Osobně mi přijde, že takovýhle průvodce vývojem aplikace je úplně ten nejlepší způsob, jak uvést začátečníka do nějaké neznámé technologie.
tiez dakujem len neviem ci tych novych technologii neni privela na jeden serial. potom sa clovek uci 3 veci naraz a nakoniec nepochopi ani jedno. namiesto angualru by som pouzil jQuery a na angular by som vyhradil samostatny serial. to iste plati pre mongo radsej by som pouzil osvedcene MySQL.
Samostatné seriály se mohou objevit, pokud je někdo napíše. Já jsem naopak za hypermoderní seriál rád, aspoň vynikne to srovnání s těmi konzervativnějšími.
Tenhle seriál se mi líbí právě proto, že se autor nesnaží jen manuálovitě popsat node.js, ale ukazuje i styl vývoje a k tomu ta hromada „nových technologií“ patří.
Tento díl byl trochu výjimečný v tom, že těch nových technologií bylo trochu více. Ještě zhruba 2 až 3 díly se bude vytvářet vývojové prostředí (v příští díle testování a CI, v dalším díle bude něco o API a užitečných nástrojích pro vývoj v Node.js a pak ještě díl o AngularJS). Pak už budeme mít kompletní prostředí pro vývoj a budeme rychleji postupovat v tvorbě e-shopu.
Ačkoliv je zde mnoho nových věcí, každá část kódu by měla být popsána podrobně tak, aby ji každý rozuměl. Jsou tady screenshoty, každý postup je snad detailně popsán a odzkoušen ještě minimálně jednou osobou. Navíc je zde po každém díle demo aplikace, takže začátečník by snad neměl mít žádný větší problém. A pokud ano, může využít k dotazům zdejší diskusi.
jQuery bohužel nelze použít, to se hodí pouze na jednodušší věci kolem nějakého HTML elementu a lze ho s Angularem dobře kombinovat. Angular je MVC framework a výrazně snižuje množství kódu, které je potřeba napsat. Mám dojem, že Vojta Jína, spoluautor AngularJS, uváděl, že když přešel Google u nějakého projektu na Angular, tak se snížilo množství kódu v JavaScriptu na 10% původní velikosti.
Podobné je to s MongoDB. Dělal jsem e-shop systém, který je dnes nasazen na cca 100 e-shopech a vím, kolik práce dalo vyřešit některé pro e-shop charakteristické úlohy v MySQL a to samé lze s MongoDB vyřešit mnohem snáze.
Není to tedy o tom, že zde použiji MongoDB nebo AngularJS jen proto, abych ukázal něco nového, ale je to proto, protože si myslím, že je to to nejlepší, co lze dnes použít.
html boilerplate ma v URL na konci medzeru „http://html5boilerplate.com/ “ takze sa to na serveri nenajde.
To nám uteklo, opravil jsem to, díky.
Bohuzel se musim pridat k tem, kteri si mysli, ze uz je tady tech knihoven moc. Moc nerozumim pouziti NodeJS. V tutorialu Node jen abstrahuje databazi coz je uloha pro modul v Angularu.
Děkuji za názor. Odpověď by byla na delší dobu, takže napíšu článek na svůj blog a vložím sem pak odkaz.