Install the Firebase node/npm packages.
$ sudo npm install -g firebase
$ sudo npm install -g firebase-tools
In the Firebase interface, you should be able to navigate to the general settings in your project (the gear icon). From there you should be able to find a link that says something to the effect of "Add Firebase to your web app". Click this and copy the general connection code:
var firebase = require("firebase");
var config = {
apiKey: "AIzaSyDBASDSDDF23SDVAS_ZmGSfM",
authDomain: "foo-bar42.firebaseapp.com",
databaseURL: "https://foo-bar42.firebaseio.com",
storageBucket: "foo-bar42.appspot.com",
messagingSenderId: "61922992922259161"
};
firebase.initializeApp(config);
// get a reference to the database service
var database = firebase.database();
The previous example logs you in as a regular user and you are subject to the database access rules you setup. You can also access Firebase through the admin SDK, which will give you full access irrespective of the database access rules.
var admin = require("firebase-admin");
admin.initializeApp({
credential: admin.credential.cert(".adminServiceAccountKey.json"),
databaseURL: "https://bookmrks-e4ff5.firebaseio.com"
});
var database = admin.database()
Here are a couple of simple repl scripts that launch an interactive Node.js shell with the firebase configs above preloaded.
You can grab a onetime set of data from the database. The ref
is the location of the collection. In this case it's the grabbing the "bookmarks" collection, accessible at this URL: https://bookmrks-e4ff5.firebaseio.com/bookmarks.json
.
once returns a Firebase.Promise. It accepts the event type to listen for. In this case we're listening for the "value" event, which just means we're looking for the value. Once listens for exactly one event and then stops listening. The success callback of the promise passes in a DataSnapshot. You can then get the data off of this object, with the val method.
let bookmarksRef = database.ref('bookmarks');
let bookmarks;
bookmarksRef.once('value', function(dataSnapshot) {
bookmarks = dataSnapshot.val();
});
on works just like once
except it is a subscription that will continue to be updated everytime the event ("value" in this case) changes. This will give your app some real-time-ness. Since this is essentially a subscription, you will need to call the off method to cancel the subscription:
Note: This will add the complete hash of bookmarks to the bookmarks variable.
let bookmarksRef = database.ref('bookmarks');
let bookmarks;
let onValueChange = bookmarksRef.on('value', function(dataSnapshot) {
bookmarks = dataSnapshot.val();
});
// then cancel the subscription with
onValueChange.off()
You can get a child of a ref. Here I'm getting a single bookmark by using a child ref off of the bookmarksRef
(the "0" is the key of the bookmark):
let firstBookmarkRef = bookmarsRef.child('0')
let bookmark;
firstBookmarkRef.once('value', function(dataSnapshot) {
bookmark = dataSnapshot.val();
});
When you get back a dataSnapshot
for a collection, you may still want to iterate through them and do something for each. For example, you may want to translate each into an object. In this example, we're creating an array of Bookmark
objects from the returned dataSnapshot
:
let bookmarks: Bookmark[] = [];
database.ref('bookmarks').on('value', function(dataSnapshot) {
let bookmarksHash = dataSnapshot.val();
Object.keys(bookmarksHash).forEach((key) => {
bookmarks.push(Bookmark.fromHash(bookmarksHash[key]));
});
});
You can use the orderByChild method on a ref. This will allow you to sort by a child key of a collection of objects.
The parent dataSnapshot.val()
returns a JSON object, where the order is not sorted. To get the sorted order, you need to loop throught the child refs and build up an array.
The only available order is ascending. If this is okay, push
onto an array. If you want descending unshift
onto an array.
Also keep in mind that the string sorting is case sensative lexographic, so Z
comes before a
in the sort order. If you need a case insensitive sort, store another child value that is all lower case. For example, a bookmark could have a title
and a title_lower_case
.
var bookmarks = [];
bookmarksRef.orderByChild('title').once(function(dataSnapshot) {
dataSnapshot.forEach(function(child) {
bookmarks.push(child.val());
});
});
You are able to grab by a single key in Firebase. This is essentially like a one-off where
clause, allowing you to grab a subset of a collection where one of the child keys is equal to some value. For example, this shows how to grab all bookmark nodes that have uid
value set to a specific user's uid
:
var bookmarks = [];
bookmarksRef.orderByChild('uid').equalTo('fn9QGFdlHKMuraGEEGLHjTi3SoW2')
.once(function(dataSnapshot) {
dataSnapshot.forEach(function(child) {
bookmarks.push(child.val());
});
});
There is the numChildren method on the dataSnapshot object:
var bookmarksCount = 0;
bookmarksRef.once('value', function(dataSnapshot) {
bookmarksCount = dataSnapshot.numChildren();
});
You can limit the number of results you pull back with either limitToFirst or limitToLast methods on the ref.
var lastFiveBookmarks = [];
bookmarksRef.limitToLast(5).once('value', function(dataSnapshot) {
lastFiveBookmarks.push(dataSnapshot.val());
});
We can add a new item to a collection by using the push method on a collection ref and then using the set method to update values.
Notice the use of firebase.database.ServerValue.TIMESTAMP
to add the current timestamp. You probably don't want to rely on a value set in your app, so Firebase provides this value.
var newBookmarkRef = bookmarksRef.push();
newBookmarkRef.push({
createdAt: firebase.database.ServerValue.TIMESTAMP,
title: 'Firebase documentation',
url: 'https://firebase.google.com/docs/',
tags: ['firebase', 'nosql']
});
var newBookmark;
newBookmarkRef.once('value', function(dataSnapshot) {
newBookmark = dataSnapshot.val();
});
You can also just call push
directly if you don't need the ref:
bookmarksRef.push({
createdAt: firebase.database.ServerValue.TIMESTAMP,
title: 'Firebase documentation',
url: 'https://firebase.google.com/docs/',
tags: ['firebase', 'nosql']
});
You can also pass an optional callback function as the second argument to the push
method:
let bookmarkData = {
createdAt: firebase.database.ServerValue.TIMESTAMP,
title: 'Firebase documentation',
url: 'https://firebase.google.com/docs/',
tags: ['firebase', 'nosql']
}
bookmarksRef.push(bookmarkData, (error) => {
if (error) {
console.log(`ERROR: ${error}`);
} else {
console.log('push successful!');
}
});
First you'll get a ref for a collection item:
var bookmarkRef = database.ref('bookmarks/-KWkxBFE0js_G-mdX98Q');
var bookmark;
bookmarkRef.once('value', function(dataSnapshot) {
bookmark = dataSnapshot.val();
});
To update the whole record you can call the set method on the collection item's ref:
bookmark.title = 'New Title of Awesomeness';
bookmarkRef.set(bookmark);
This will update the entire document with what you pass in. so if you pass in { title: 'New Title of Awesomeness' }
, the whole bookmark collection item will be replaced with an object with a single title value.
To update a single field/value for a collection item, you can use the update method:
bookmarkRef.update({ title: 'New Title of Awesomeness' });
You can add or update multiple nodes at one time. The updates are atomic. Either they all pass or they all fail.
Here is an example where we're creating multiple new nodes at the same time, one for a master feed of posts and one for a user's own feed:
function newPost(uid: string, title: string, content: string): firebase.Promise {
let postData = { uid, title, content };
let postKey = firebase.database().ref.child('posts').push().key;
let updates = {};
updates[`feed/${postKey}`] = postData;
updates[`users/${uid}/feed/${postKey}`] = postData;
return firebase.database().ref().update(updates);
}
This is as easy as calling the remove method on a collection item's ref (scary):
bookmarkRef.remove();
Both set
and update
will update the value regardless of the current value. Usually this is fine, however in the case of a counter increment (imagine Facebook likes) you may not have a current value to increment (imagine lots of people favoriting at the same time). You can use the transaction method to deal with this.
// say our bookmarks app is social and people can "like" our bookmarks
var bookmarkRef = database.ref('bookmarks/-KWkxBFE0js_G-mdX98Q/favorites');
bookmarkRef.transaction(function(currentFavorites) {
return currentFavorites + 1;
});
Firebase provides a pre-packaged authentication system for your app. When using this, users are created in the Firebase system. However, these user "records" are only for authentication. You will probably want to store additional user related data in your database. To do this, you need to create your own users collection and associate collection items with the user records via the user record's uid.
In order for this to work, I had to create a utility web page to call the Google auth popup. The web page can not just be served from the local filesystem, it has to be served with a web server with the HTTP protocol.
Using the simple python server:
$ http-serve
Then open the web page: http://localhost:8080/create-app-user.html
The rules system gives you a declarative mechanism to define access privileges and schema validation to your database.
Rules are defined on a per-node basis.
Rules are written in a JSON file. If you are hosting on Firebase and deploying with the firebase deploy
command, this file can be stored in revision control and it will be applied on each deploy.
{
"rules": {
"foo": {
".read": true,
".write": false
}
}
}
In this example, anyone is granted read access to the foo collection, or the /foo
path, but no one is granted write access.
Concider the following example:
{
"rules": {
"foo": {
".read": true,
"bar": {
".read": false
}
}
}
}
Even though we've specified that reading for bar
is false, the shallower parent node has granted read access. The highest level rule that grants read access takes precedence.
However, if we reverse this example...
{
"rules": {
"foo": {
".read": false,
"bar": {
".read": true
}
}
}
}
... access is granted for bar, even though it is not granted on the parent. So, when evaluating if access is granted, Firebase walks down the rule tree looking at each node in the hierarchy. The first node it encounters that grants access is accepted and the system quits descending the tree.
These two examples are the same:
{
"rules": {}
}
{
"rules": {
".read": false,
".write": false
}
}
data
and newData
are from a list of predefined variables that can be used in your rules.
".write": "!data.exists() || !newData.exists()"
One of the core Angular developers published this gist which provides the code for generating pushkeys. This might be useful if you are creating fake data.
Here is a good StackOverflow response about data modeling in Firebase, written by a Firebase engineer. He goes over the different tradoffs when doing basic data modeling in different ways with Firebase.
The idea is that if you need to query data from multiple places, it may be more performant to write this data to these locations, duplicating and denormalizing the data. This may increase your read performance.
For example, say you have a Twitter clone. Your twitter feed is the culmination of all of your tweets mixed in with all of the tweets from people you are following. In a tranditional RDBS you might have a single Tweets table. Each Tweet would have a user ID associated with it. To ge the feed, you might get a list of user IDs for users who you are following and then you could select all tweets with user ID IN()
the set.
However, Firebase doesn't give you the ability to do IN()
queries or even WHERE
statements. If you stored all tweets in a single master feed, to get a user's feed you would need to grab tweets for each user individually and then manually assemble and sort the tweets to compose the feed. To do this in Firebase, you might have a feed for each user. When you save a tweet, it's added to your feed and it is added/duplicated onto the feed of every user who's following you. This is "fanning-out".