JavaScript na serveru: MongoDB, Mongoose a AngularJS

node.js logo

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ů)

  1. JavaScript na serveru: Začínáme s Node.js 23.11.2010
  2. JavaScript na serveru: Patří budoucnost Node.js? 21.9.2012
  3. JavaScript na serveru: Architektura a první Hello World 5.10.2012
  4. JavaScript na serveru: moduly a npm 12.10.2012
  5. JavaScript na serveru: začínáme programovat e-shop 19.10.2012
  6. JavaScript na serveru: MongoDB, Mongoose a AngularJS 26.10.2012
  7. JavaScript na serveru: Testování a kontinuální integrace 2.11.2012
  8. JavaScript na serveru: REST API 9.11.2012
  9. JavaScript na serveru: implementace REST API 16.11.2012
  10. JavaScript na serveru: nástroje a dokumentace 23.11.2012
  11. Začínáme s AngularJS 30.11.2012
  12. AngularJS direktivy a testování 7.12.2012
  13. JavaScript na serveru: CoffeeScript a šablonovací systémy 14.12.2012

Š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!

Jakub pracoval na několika zajímavých projektech, za nejvýznamnější považuje vytvoření e-commerce řešení Shopio.cz. Poslední rok se plně věnuje Node.js, frameworku AngularJS a NoSQL databázím.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 10

Přehled komentářů

s7anley Vďaka za seriál
Dundee5 Re: Vďaka za seriál
blizz Re: JavaScript na serveru: MongoDB, Mongoose a AngularJS
Martin Hassman Re: JavaScript na serveru: MongoDB, Mongoose a AngularJS
Honza Marek Re: JavaScript na serveru: MongoDB, Mongoose a AngularJS
Jakub Mrozek Re: JavaScript na serveru: MongoDB, Mongoose a AngularJS
andrej.k Re: JavaScript na serveru: MongoDB, Mongoose a AngularJS
Martin Hassman Re: JavaScript na serveru: MongoDB, Mongoose a AngularJS
vskiper moc je moc
Jakub Mrozek Re: moc je moc
Zdroj: http://www.zdrojak.cz/?p=3722