Firebase: krátké seznámení

Co je služba Firebase, jak se používá, jaké problémy řeší a kolik za to zaplatím? Pojďme se podívat na to, co vše Firebase nabízí a jak se pracuje s jejím primárním rozhraním, tedy JavaScriptovou knihovnou.
Nálepky:
Společnost Firebase byla založena v roce 2011 a na podzim roku 2014 ji koupil Google. Její portfolio sestává ze tří produktů: databáze Firebase, statického hostingu a autorizační infrastruktury pro přístup k databázi. V tomto článku si ukážeme produkt první a nejdůležitější, tedy realtime nosql databázi Firebase.

Logo Firebase
Dobrá zpráva pro všechny zájemce hned na začátek: základní funkcionalita Firebase je pro vývojáře zdarma. Stačí se zaregistrovat a vybrat si Hacker Plan, který nabízí 100 MB úložiště, maximálně 50 současně připojených klientů a přenos 5 GB měsíčně. Pro pokročilejší potřeby je k dispozici celá řada placených tarifů.
Registrovaný uživatel dostává možnost tvorby vlastních databází, které jsou přístupné na adrese https://nazev.firebaseio.com/ (ve zbytku článku budeme pracovat s tímto hypotetickým označením nazev, nicméně žádná taková databáze neexistuje). Otevřením tohoto odkazu se dostaneme přímo do webového rozhraní, které dovoluje databázi spravovat. Nás bude ale zajímat práce s některou z celé řady klientských knihoven, které Firebase svým uživatelům nabízí. Ty jsou k dispozici pro mobilní platformy (iOS, Android), pro web (JavaScript a transport pomocí WebSockets) a existuje také rozhraní REST. Použít lze i neoficiální knihovnu pro náš oblíbený serverový jazyk, obalující právě REST API.
Cesty v databázi a URL
Než přistoupíme ke konkrétním ukázkám práce s Firebase, musíme se seznámit s povahou dat a jejich organizací. Firebase je NoSQL, neobsahuje tedy žádné tabulky. Namísto toho do ní vkládáme stromově strukturovaná data, podobná těm, která zpracováváme jako JSON. Významným rozdílem oproti JSONu je zde absence polí: pokud potřebujeme pracovat s posloupností záznamů, vložíme je pod unikátními a seřazenými klíči do běžné struktury. Při iteraci je pak (na rozdíl od iterace v JavaScriptu) garantováno pořadí — aritmetické pro číselné klíče, lexikografické pro ostatní.
Pro odkazování na data uložená ve Firebase se používá syntaxe URL, kde jednotlivé zanořené položky oddělujeme lomítky. Jako jednoduchou ukázku tedy můžeme do databáze vložit objekt:
{
name: "Zdenda",
address: {
city: "Bruntál",
street: "Ruská"
}
}
Následně pak URL https://nazev.firebaseio.com/name
odpovídá řetězci "Zdenda"
a jméno ulice nalezneme na adrese https://nazev.firebaseio.com/address/street
. Tato URL jsou platná i v případě, že k nim v databázi neexistují žádná data — pro takové adresy je uložená hodnota null
.
Bohatší datová struktura pak může vypadat třeba jako na níže vloženém obrázku. Ten byl pořízen jako screenshot právě z webového administračního rozhraní nazvaného Firebase Forge. V něm lze data procházet a libovolně upravovat.

Firebase Forge
JavaScriptové klientské API
Přístup k Firebase přímo z prohlížeče (tj. backend-as-a-service) je lákavou technikou, kdy nepotřebujeme žádný vlastní server a zároveň ani žádnou serverovou logiku. Pro tyto účely musíme nejprve do stránky vložit klientskou knihovnu firebase.js
(v době psaní článku ve verzi 2.2.2). Její kompletní dokumentace je k dispozici online a my se podíváme, jak se s ní pracuje.
Základním objektem je Firebase
, který odpovídá konkrétnímu URL v databázi. Bývá zvykem začít u kořenového uzlu:
var root = new Firebase("https://nazev.firebaseio.com/");
Proměnná root
nenabývá hodnoty dat uložených v databázi. Je to objekt, pomocí kterého s těmito daty můžeme manipulovat.
První dovedností je navigace mezi URL. K tomu slouží metody child()
, parent()
a root()
. Pomocí nich můžeme vytvářet další instance Firebase
, odpovídající různým cestám v databázi:
var root = new Firebase("https://nazev.firebaseio.com/");
var address = root.child("address");
var street = address.child("street");
street.root().toString() == root.toString(); // true
street.parent().toString() == address.toString(); // true
Zápis a mazání hodnot
Jakmile máme k dispozici objekt popisující umístění ve Firebase, můžeme zavoláním jeho metody set()
vložit data. Ta mohou být skalární i libovolně hluboká:
var root = new Firebase("https://nazev.firebaseio.com/");
root.set({
name: "Zdenda",
address: {
city: "Bruntál",
street: "Ruská"
}
});
root.child("address/street").set("Krnovská");
Klientská knihovna se postará o zpropagování těchto nových dat na vzdálený server. Pokud nás z nějakého důvodu zajímá, kdy bude tato synchronizace dokončena, můžeme metodě set
předat jako druhý parametr vlastní callback.
Pro mazání dat použijeme hodnotu null
nebo metodu remove()
:
var address = new Firebase("https://nazev.firebaseio.com/address");
address.remove();
address.set(null); // identické
V obou těchto případech je zcela jedno, obsahuje-li odkazované umístění opravdu nějaká data. Zajímavá může být ještě metoda push()
, která vloží nová data pod automaticky vygenerovaný klíč. To se hodí v případech, kdy pracujeme s výčtem položek a chceme přidat novou. Firebase (záměrně) nezná koncept polí, neboť práce s tradičně indexovanými položkami by působila problémy při současném zápisu více klientů. Klíče generované metodou push
obsahují mimo jiné klientský timestamp a jsou tak v čase rostoucí:
var projects = new Firebase("https://nazev.firebaseio.com/projects");
var p1 = projects.push();
p1.set({name:"Projekt 1"});
var p2 = projects.push({name:"Projekt 2"});
Čtení dat a sledování změn
Firebase je realtime databáze, jejíž ambicí je udržovat data všech klientů stále synchronní. Lze proto očekávát, že JS API bude obsahovat nějakou formu notifikací či událostí, díky kterým se snadno dozvíme o vzdálených změnách.
Hlavní metodou pro tyto potřeby je on()
. Slouží k přidání posluchače na některou z databázových událostí:
var root = new Firebase("https://nazev.firebaseio.com/");
root.on("value", function(snapshot) {
/* ... zpracovat data ... */
});
Následující tabulka shrnuje typy událostí a jejich význam:
Událost | Popis |
---|---|
value |
Změna libovolných dat ve sledované položce či libovolné podpoložce |
child_added |
Přidání nového potomka v daném umístění |
child_removed |
Odebrání potomka v daném umístění |
child_changed |
Změna v datech libovolného potomka |
child_moved |
Změna v datech libovolného potomka |
Parametr námi předané funkce bývá označován jako snapshot a odpovídá stavu databáze ve chvíli události. Zajímavá je především jeho metoda val()
, která vrací aktuálně dostupná data jako JavaScriptový objekt:
var root = new Firebase("https://nazev.firebaseio.com/");
root.on("value", function(snapshot) {
var data = snapshot.val();
alert("V databází jsou tato data:" + JSON.stringify(data));
});
Vidíme, že pomocí událostí čteme data z Firebase. První dva druhy událostí (value
, child_added
) jsou navíc specifické tím, že budou po navěšení posluchače události vykonány, aniž by došlo ke změně v databázi. To odpovídá tradičnímu scénáři, kdy chceme získat aktuální data (a nečekat, než je někdo změní).
Dříve přidaný posluchač můžeme snadno odebrat metodou off()
. Pro zjednodušení je zde ještě metoda once()
, která vykoná posluchač právě jednou a následně jej odebere (jde tedy jen o zkratku za posloupnost volání on() -> callback() -> off()
).
Když se ztratí spojení
Klientské API též dovoluje definovat, co se má stát ve chvíli odpojení klienta. To odpovídá například zavření stránky v prohlížeči — je jasné, že v takovou chvíli je již pozdě na spuštění nějakého úklidového skriptu.
Vykonáním metody onDisconnect
na libovolné instanci Firebase
dostaneme objekt, který definuje operace provedené po odpojení klienta. Pokud tedy kupříkladu v hypotetické chatovací aplikaci udržujeme data právě připojeného klienta v cestě /zdenek
, možná budeme chtít tento objekt po odpojení vymazat:
var zdenek = new Firebase("https://nazev.firebaseio.com/zdenek");
zdenek.onDisconnect().remove();
Metoda onDisconnect
dovoluje ještě další zajímavé triky, které lze nastudovat v oficiální dokumentaci.
Každý může všechno‽
Databáze Firebase je ve výchozím nastavení přístupná všem klientům. Kdokoliv se k ní může připojit a provádět v ní libovolné změny. Takové nastavení je dobré pro experimenty, ale v seriózním provozu bychom se s ním daleko nedostali.
Firebase pro tyto případy používá jednoduchý, ale robustní bezpečnostní model. Pro každou cestu v databázi můžeme individuálně vyjmenovat uživatele, kteří do ní mají právo zápisu či čtení. Pro zápis lze navíc definovat ještě omezení vkládaných dat (formou validačních funkcí) a provádět tak jejich validaci.
Tato bezpečnostní pravidla definujeme ve webovém rozhraní pro správu databáze. Popis formátu pravidel i s ukázkami je k vidění v dokumentaci.
Aby tato pravidla mohla fungovat, je nutné identifikovat jednotlivé uživatele. To je sama o sobě komplexní problematika, která by si možná zasloužila vlastní článek. Pojďme se na ni nyní podívat jen v kostce:
var root = new Firebase("https://nazev.firebaseio.com");
root.authWithOAuthPopup("twitter", function(error, authData) {
if (error) {
console.log("Chyba!", error);
} else {
console.log("Přihlášen/a jako:", authData);
}
});
Pokud proběhne přihlášení úspěšně, bude při přístupu do databáze uživatel následně identifikován svým Twitterovým účtem. Na jeho základě pak můžeme pracovat s bezpečnostními pravidly a povolit tak třeba zápis jen některým uživatelům.
Firebase nabízí tyto autorizační metody:
Funkce | Popis |
---|---|
authWithOAuthPopup |
Přihlášení pomocí OAuth přes Facebook, GitHub, Google či Twitter. Dialog proběhne ve vyskakovacím okně. |
authWithOAuthRedirect |
Jako v předchozím případě, ale dialog proběhne po přesměrování na stránce poskytovatele |
authWithOAuthToken |
Přihlášení pomocí OAuth tokenu (stejní poskytovatelé jako výše) |
authWithPassword |
Přihlášení jménem a heslem; uživatelské účty automaticky spravuje Firebase |
authWithCustomToken |
Autorizace proti vlastní databázi uživatelů pomocí JSON Web Tokens |
authAnonymously |
Dočasné falešné anonymní přihlášení s náhodně zvoleným identifikátorem klienta |
A k čemu to celé je?
Na konci krátkého seznámení s Firebase možná zvažujete, k čemu by se taková služba mohla hodit. Na webu lze najít celou řadu vzorových aplikací: Firechat, Firepad, Office Mover… všechny staví na principu okamžitého sdílení dat mezi uživateli, ale Firebase je vhodná i tam, kdy nám o real-time zas tak moc nejde. Hlavní výhody Firebase a jejího JS klienta jsou:
- Backend as a service, bez vlastního serveru
- Automatické zálohování, garance dostupnosti, parametry dle tarifu
- Datový model koresponduje s JavaScriptovými objekty
- Notifikace o změnách
- Vždy dostupná data, knihovny pro X jazyků a frameworků
- Abstrakce nad autorizačními technologiemi všech známých poskytovatelů
Máte s Firebase nějakou zajímavou zkušenost? Napadlo vás dobré využití? Znáte konkurenční službu, která stojí za zmínku? Dejte vědět v komentářích!
Firebase je super, používám skoro rok. Ale pár věcí mi pije krev. Třeba budeš vědět.
1) Nemožnost rozlišit client a server update. Rozumím, že je to by design, ale stejně. Měl jsem aplikaci, ve které se ukládal a propagoval app state do localStorage. Tohle zajišťuje sync mezi taby. Rozedituješ jeden formulář, a v druhém okně máš ten samý stav. Nakonec jsem musel použít škaredý hack. Dokonce jsem to řešil s vývojáři, stačil by malý bool
fromServer
, ale bohužel mají jiné priority.2)
on/off
. Pokud chci něco začít poslouchat a později to zase vypnout, chtělo by to nějakou toggle metodu, nakonec jsem si taky musel napsat.1) Rozumím. Taky vidím v odlišení vzdálených a lokálních změn trochu problém, ale na druhou stranu se dá symetricky prohlásit, že si to dovede rozlišit i ta komponenta sama.
Tedy pokud mám řekněme formulářové pole, tak při jeho změně uživatelem se provede propagace změny do Firebase, zatímco při změně zvenčí (tj. v důsledku události „value“) se jen aktualizuje UI a nevznikne tak nekonečný cyklus „set -> firebase change -> ui change -> set -> …“.
Pokud není vhodné UI updatovat ve chvíli, kdy by se jednalo o NOOP, pak bude nutné přidat detekci tohoto stavu (remote data se shodují s lokálními) a update ignorovat.
Mimochode, v tomhle popsaném případě jsem úplně nepochopil tu localStorage. Proč synchronizovat stav aplikace mezi záložkami pomocí localStorage, když se pro to (zároveň) může použít Firebase? Nejsou to dvě (kolidující) technologie pro tu samou úlohu?
Co tak koukám, tak mi Firebase obecně přijde méně vhodná tam, kde mnoho klientů šahá na ta samá data. Její síla leží spíš v inkrementálních updatech, kdy každý klient vloží nový záznam. Drží si tak posluchače v rodiči na „child_added“, sám volá push() a dovede tento lokální update detekovat tím, že nově přidané dítě odpovídá výsledku volání push. Koneckonců to odpovídá i těm neměnným datovým strukturám, které poslední dobou tolik frčí :-)
2) Ano, řešení „rozděl a panuj“ popsané v druhém komentáři mi přijde jako správná cesta. U on/off se mi zdá trochu nepohodlné, že si musím ten callback někde držet, abych ho mohl následně odebrat (off lze volat také bez parametru a zruší to všechny posluchače, ale to nemusí vždy vyhovovat). Upřednostil bych spíš něco typu interface EventListener, kde bych předal
this
a měl tak navíc zadarmo (i v ES5) bind.Ad „Proč synchronizovat stav aplikace mezi záložkami pomocí localStorage, když se pro to (zároveň) může použít Firebase?“
Jsou a kolidují, ale důvod přesto existuje – Pokud automaticky okamžitě promítáš stav appky do localStorage, nikdy nejpřijdeš o rozeditovaná data, ani při náhodném zavření prohlížeče nebo výpadku elektriky nebo chybě. Zároveň je velmi praktické, aby všechny taby jedné aplikace sdíleli ta samá data, protože to předchází problém typu omylem jsem v jednom tabu něco smazal a v druhém to dal editovat. Firebase zůstala na půli cesty, funguje pouze pokud je dostupné spojení a nepoužívá localStorage tak, jak jsem popsal. Propojit tyhle dvě technologie bez smyčky je možné, ale pár dnů mi to zabralo. Model appky prostě dle mého patří do localStorage nebo jiného úložiště, pak můžeš pracovat s appkou i offline. Tohle Firebase nepodporuje a je to škoda. Jak si říkal, porovnávat data, to moc nelze v případě lokálně dočasně nastaveného server time, tam ti Firebase prostě dá číslo, a ty nikdy nevíš, jaké. Existuje jediný možný a velmi škaredý hack, a to je pomocí setTimeout. Psal jsem si kvůli tomu s vývojaři, ale mají bohužel zcela jiné priority.
Ad 2. tohle můžeš snadno vyřešit něčím jako https://gist.github.com/rarous/34856a3bfe628eb81c37, případně to spojit s RxJS nebo Baco.js, které tohle dělají OOTB.
…pak si ale zase musím někde držet návratovou hodnotu od
subscribe()
.Jasne ze je technicky mozne privazat input policko primo do FireBase, ale proc bys to sakra delal? Krome online editace stejneho dokumentu to snad ani nedava smysl. Proste si z modelu nactes vetu do objektu formulare, delas na nem vstupni kejkle, validujes vstupy a kdyz mas cely objekt v kupe posles ho do FireBase (zaznam na serveru zamknes jednoduchym bullem). Vyhnes se tak problemum, ktere popisujes, nakrasne si muzes data ulozit na lokale a kdyz se bude aplikaci darit jednoduse jednou vymenis FireBase za jinou databazi. FireBase neni jedina cesta k three-way-data/bindingu. Porad mas k dispozici Meteor, a/nebo Pouch/Couch dvojce. Osobne na Firebasi pisu jenom prototyp a jakmile se trochu utrese struktura modelu, prepisuju servisy na Mongo/CouchDB (podle zadani). Abych pak nemusel prepsat celou apku tak stejne i v pripade FireBase pisu jednoduchou factory.
Firebase je super pro mobilni aplikace, Zrovna pisu takovej proof of concept ktery z FireBase cte nejen data ale i kod. a sablony. Jako prvni se nacte popis aplikace, UI-router objekt (Angular), a OAuth. Diky zatracovanym angular modulu jsou vsechny controlery a servisy vlastne jenom objekty, kterym se extenduje master module, takze na mobilu bezi vpodstate jenom runtime, ktery si vsechno ostatni dotahne. Kdyz to vezmes ad absurdum, tak ti na mobilu bezi apka, ktera se meni s tim, jakej kod ti parta pankacu live uklada do Firebase ;))
Ad 2) Tak teď mne napadla děsivá myšlenka – asi jsem to dělal blbě. Takhle, podle routy jsem různě konfiguroval nahrávání různých dat z Firebase, a ručně jsem je zase přestával poslouchat. Jenže, když se podívám na Facebook Relay, tohle je věc user interface, které se v čase dost mění, a udržovat bokem co se má kdy nahrávat je dost komplikované. Pokud ale u každé React komponenty deklarativně označím, co se má na componentDidMount na Firebase registrovat, a co na componentWillUnmount deregistrovat, pak za mne React elegantně řeší celou správu on/off, takže z tohoto pohledu je Firebase api asi naprosto v pořádku. Člověk se furt učí.
vypadá to, že by se nad tím krásně dala postavit nějaká mlutiplayer hra