Update (October 3, 2014): Firebase Simple Login has been deprecated and is now part of the core Firebase library. Use these links to find the updated documentation for the web, iOS, and Android clients.
This post was featured as a guest post on the PhoneGap blog.
Many developers have been using Firebase in their PhoneGap applications since our beta launch nearly a year ago. We know many of you encountered some rough edges, so we've put a lot of work into making sure the experience of developing with PhoneGap and Firebase is a smooth one.
Today, we're announcing that the Adobe PhoneGap / Apache Cordova platform is fully-supported by Firebase and works out of the box to offer the full suite of functionality that Firebase provides on the web.
Aligned Philosophies
At Firebase, we're big believers in the power and flexibility in writing applications entirely using client-side code without managing your own servers. PhoneGap embodies this same philosophy in enabling developers to write mobile applications for a broad suite of devices using only client-side JavaScript.
PhoneGap goes further by providing rich APIs for accessing native device features, such as geolocation services or the device camera, that previously required writing device-specific code in the native platform language.
Getting Started
With Firebase, you no longer need to store all of your data locally on the device itself or manually sync it with a remote server using AJAX and your own authentication logic. Firebase makes it easy to enable real-time data synchronization in your PhoneGap application with a simple JavaScript include.
Ready to start building? Head to our web quickstart guide.
What's New
Firebase Simple Login is now fully supported in PhoneGap with the release of PhoneGap 2.3.0, making use of the InAppBrowser for OAuth-based authentication. You can now use Facebook, Twitter, GitHub, and password-based authentication in your PhoneGap application purely with a script include, free from the hassle of configuring application domains or bundle IDs. To see it in action, clone our sample repo and head to the documentation for Firebase Simple Login.
Update (April 2, 2015): While this post still contains some useful and relevant information, we have since released advanced query functionality and added documentation on best practices for structuring data in Firebase.
This is the second in a series of blog posts on Architecting your application with Firebase.
One of the questions users frequently ask us is — "How do I query my Firebase data for X?" — where X often resembles a SQL query. This is a natural question, especially when coming to Firebase from a relational database background.
Firebase essentially has two ways to query for data: by path and by priority. This is more limited than SQL, and there's a very good reason for that — our API is carefully designed to only allow operations we can guarantee to be fast. Firebase is a real-time and scalable backend, and we want to enable you to build great apps that can serve millions of users without compromising on responsiveness.
This makes it important to think about how you will need to access your data and then structure it accordingly. This blog post will help you do that!
Primitives
Firebase comes with two powerful query mechanisms which you should be familiar with. See our Data Structure and Query docs for details, but as a reminder:
You can query data by its location. You can think of the location as being a primary index (In SQL terms, ref.child('/users/{id}/') is roughly equivalent to SELECT * from users WHERE user_id={id}). Firebase automatically sorts data by location, and you can filter the results with startAt, endAt, and limit.
ref.child('/users/{id}/')
SELECT * from users WHERE user_id={id}
You can query data by its priority. Every piece of data in Firebase can be given an arbitrary priority, which you can use for whatever you want. You can think of this as a secondary index. You might, for example, store timestamps representing a user's date-of-birth as priority and then query for users born after 2000 with ref.child('users').startAt(new Date('1/1/2000').getTime()).
ref.child('users').startAt(new Date('1/1/2000').getTime())
With these two primitives in mind, we can start thinking about how to structure your data.
Structure is important
Before writing any code for a Firebase app, I spend some time thinking about how I'm going to structure the data. The benefit of this is two-fold — first, it makes it much easier to reason about Security and Firebase Rules when the time comes; and second, it forces me to think about the queries my app will need. A well designed tree structure is crucial to an elegant app.
The best way to learn is by doing, so let's take an example. We're going to design the data structure for a site that lets you post links and comment on them (think Hacker News or Reddit).
We'll discuss how this app would be designed if it were backed by a SQL database first and then approach the same problem in a Firebase friendly way. If you aren't familiar with SQL or just want to know how to structure the data in Firebase, feel free to skip the section on SQL!
In a SQL world
If you were writing this application with a SQL backend, you will probably have a table of users:
CREATE TABLE users ( uid int auto_increment, name varchar, bio varchar, PRIMARY KEY (uid) );
a table of posted links:
CREATE TABLE links ( id int auto_increment, title varchar, href varchar, submitted int, PRIMARY KEY (id), FOREIGN KEY (submitted) REFERENCES users(uid) );
and finally, a table of posted comments:
CREATE TABLE comments ( id int auto_increment, author int, body varchar, link int, PRIMARY KEY (id), FOREIGN KEY (author) REFERENCES users(uid), FOREIGN KEY (link) REFERENCES links(id) );
Rendering the home page of the site would require you to get a list of the latest links submitted:
SELECT * FROM links ORDER BY id DESC LIMIT 20
Displaying all the comments (latest first) for a particular link would be something like:
SELECT * FROM comments WHERE link = {link_id} ORDER BY id DESC
Whenever you want to display user information (on the user profile page, or the user name under a comment), you'll fetch that from the users table:
SELECT * FROM users WHERE uid = {user_id}
Let's say you also want to display all the comments made by a particular user on their profile page. That's easy:
SELECT * FROM comments WHERE author = {user_id}
Notice that we will be accessing comments in two different ways (by link_id and by author). We'll see the implications this has with Firebase momentarily.
link_id
author
You'd also have a set of INSERT statements whenever a comment is posted, a link is submitted, or a new user signs up. This should cover most aspects of the app (the devil is in the details, you need to sanitize all the incoming data and so on, but let's ignore all that for now).
INSERT
In a Firebase world
If I had to write the same app using Firebase, I might start by simply replicating the structure I came up with for the SQL version. Let's have three top-level keys, one each for users, links and comments:
users
links
comments
{ users: { user1: { name: "Alice" }, user2: { name: "Bob" } }, links: { link1: { title: "Example", href: "http://example.org", submitted: "user1" } }, comments: { comment1: { link: "link1", body: "This is awesome!", author: "user2" } } }
Rendering the home page is easy enough, you simply fetch the last 20 links submitted using a limitToLast() query:
limitToLast()
var ref = new Firebase("https://awesome.firebaseio-demo.com/links"); ref.limitToLast(20).on("child_added", function(snapshot) { // Add link to home page. }); ref.limitToLast(20).on("child_removed", function(snapshot) { // Remove link from home page. });
Notice that we're already seeing the benefits of using Firebase. By handling child_added and child_removed events, the home page will automatically update in real-time as items are added without the user touching the refresh button!
child_added
child_removed
Now what happens when you want to retrieve all the comments associated with a particular link? In SQL, each comment had a reference to the link it was tied to, and you could use WHERE link = {link_id} to retrieve them, but Firebase has no WHERE query. As structured, we can only access the comment if we know its comment ID, which we do not.
WHERE link = {link_id}
WHERE
This is the crux of a Firebase-friendly data structure: you sometimes need to denormalize your data. In this case, to enable retrieving the list of comments for a link, I'll explicitly store that list with each link:
{ links: { link1: { title: "Example", href: "http://example.org", submitted: "user1", comments: { comment1: true } } } }
Now you can simply fetch the list of comments for any given link and render them:
var commentsRef = new Firebase("https://awesome.firebaseio-demo.com/comments"); var linkRef = new Firebase("https://awesome.firebaseio-demo.com/links"); var linkCommentsRef = linkRef.child(LINK_ID).child("comments"); linkCommentsRef.on("child_added", function(snap) { commentsRef.child(snap.key()).once("value", function() { // Render the comment on the link page. )); });
We also wanted to display a list of comments made by every user on their profile page. So we do something similar:
{ users: { user2: { name: "Bob", comments: { comment1: true } } } }
Duplicating data like this can be counter-intuitive to many developers. However, in order to build truly scalable applications, denormalization is almost a requirement. Essentially, we are optimizing our data reads by writing extra data at write-time. Consider that disk space is cheap, but a user's time is not.
Considerations
Let's discuss some consequences of a structure like this. You will need to ensure that every time some data is created (in this case, a comment) it is put in the right places:
functon onCommentSubmitted(comment) { var root = new Firebase("https://awesome.firebaseio-demo.com"); var id = root.child("/comments").push(); id.set(comment, function(err) { if (!err) { var name = id.key(); root.child("/links/" + comment.link + "/comments/" + name).set(true); root.child("/users/" + comment.author + "/comments/" + name).set(true); } }); }
(If you want to be notified when both the set methods have completed, consider using a library like TameJS to compose multiple asynchronous operations.)
set
You'll also need to think about how you want to handle deletion and modification of comments. Modification of comments is easy: just set the value of the comment under /comments to the new content. For deletion, simply delete the comment from /comments — and whenever you come across a comment ID elsewhere in your code that doesn't exist in /comments, you can assume it was deleted and proceed normally:
/comments
function deleteComment(id) { var url = "https://awesome.firebaseio-demo.com/comments/"; new Firebase(url + id).remove(); } function editComment(id, comment) { var url = "https://awesome.firebaseio-demo.com/comments/"; new Firebase(url + id).set(comment); }
Firebase does all it can to make these kinds of operations effecient. For example, if you've already fetched the content of a comment for a link's page, and subsequently navigate to the profile page of the user that made the comment, refetching the value will be fast since it'll be returned from the local cache.
If you're looking for a full-featured example that applies these techniques, please take a look at Firefeed. Firefeed is a twitter clone that uses denormalization to build a complex application entirely client side.
Please don't hesitate to get in touch with us if you have any questions or want to discuss the appropriate architecture for your own app. We're available on Email, Twitter, Facebook, Stack Overflow and Google+.
Today we’re really excited to announce Firepad, a Firebase-powered open source collaborative text editor.
Firepad provides true collaborative editing, complete with intelligent OT-based merging and conflict resolution. It’s full-featured and has support for both rich text and code editing. Some of its features include cursor position synchronization, undo / redo, text highlighting, user attribution, presence detection, and version checkpointing.
Firepad is powered by Firebase, so it’s built entirely with client-side code. You can add it to any application simply by adding the JavaScript code to your project. You don’t need to configure servers or write any server-side code. For example, the Firepad website itself is composed entirely of static content and is served from GitHub Pages.
Since it’s powered by Firebase, Firepad provides all of the features you would normally expect from a Firebase app: low latency updates, offline mode support, first-class security controls, automatic scaling, and easy data accessibility. Firepad stores the full revision history of your document in a Firebase database, so it’s easy to integrate Firepad documents into larger apps. Your data is available in all of the normal ways: through client libraries, the REST API, and your App Dashboard.
We’ve put particular effort into providing an easy-to-use JavaScript API for Firepad so that you can integrate it into your apps. Firepad is based on the powerful CodeMirror editor, so you’ll have access to all of its APIs as well.
We Heard You Loud and Clear
After our launch last year we saw a slew of activity around Firebase and real-time text editing. It quickly became apparent that this was the primary use-case for many of our developers. After seeing at least a dozen separate implementations, we thought it was time to lend a hand.
Building a correctly-implemented, full-featured collaborative text editor is a difficult task, even with the help of Firebase. Since our team already had some expertise in this area (It helped that I previously led the editor team for Visual Studio at Microsoft), we decided that the best approach would be to build our own complete version as an open source project. This way our developers could start their projects from a solid foundation without needing to take the time to build their own.
What’s the Big Deal?
High-quality collaborative document editing has so far been the domain of only a select few (generally large) companies. With Firepad, we aim to change that. Our goal is to enable any developer to build a high quality collaborative experience, whether her company is as big as Google or not.
We’re already off to a good start. Socrates.io, Atlassian, and Action.io have put Firebase into production, and many more apps are in the works. If you want collaborative editing in your app, now is a good time to give it a try. There’s no server software to write or infrastructure to set up; just drop in the JavaScript and go.
Credit Where Credit is Due
Firepad was not built from scratch. We depend on the CodeMirror editor to actually render the document. We chose CodeMirror as we believe it has the best API of any open source editor.
We also borrowed from the code and concepts of ot.js. Thank you Tim Baumann!
The Future
Our launch of Firepad today is just the beginning. We will continue to actively develop Firepad to ensure it remains the best way to add real-time collaboration to your text editing app.
We hope the community will help us on this mission. Firepad is on Github, and all of the code is MIT-licensed, so feel free to fork and play to your heart’s content. We’ve even included a test suite to help you catch any bugs. And, if you build something great, please send us a pull request!
One area where we expect to see significant interest is in extending our support for rich text. Our rich text operational transform code is designed to be easily extended, so if you want to add additional formatting options, just jump in and give it a try. We’ll soon be adding new features here as well, including bulleted lists, text alignment options, and more.
We look forward to seeing what you build! As always, we love feedback, so please get in touch with your thoughts, comments, and suggestions.
You can try Firepad live in your browser, read the documentation, and more at Firepad.io.
Updates
See coverage in Wired, TechCrunch, GigaOm, and TechCocktail.
Firepad now support the Ace editor! Read about getting started with Ace in the Firepad documentation.
Follow @firebase