Ever since Thomas Boyt created a Firebase Ember Data adapter, our community started asking about an official Firebase integration for Ember. We're excited to release EmberFire today: official Firebase bindings for Ember Data! EmberFire makes it easy for developers to add a real-time backend to their Ember application with just a few lines of code. Check out the blog example, and then dive into code to wire up your app's Ember backend - no servers required.
As the Ember community grows, many companies (including Square, Zendesk, and Groupon) are using the framework to build ambitious, asynchronous web applications. A few weeks back, Tom and Yehuda from the Ember Core Team came by the Firebase offices and worked with us to develop a first-class integration for Firebase. Tom explains the benefits this integration will bring to developers:
“By freeing developers from the constraints of the backend, Firebase unlocks a whole new category of sophisticated, client-side JavaScript applications. Now, with first-class support for Ember.js, those developers can continue pushing the boundaries of what's possible in the browser by leaning on the strong architectural features of Ember that lead your app towards clean separation of concerns instead of messy spaghetti. (Let's also not forget terrific support for URLs out of the box.) Ember's always been about building ambitious web applications, and this collaboration with Firebase only strengthens that idea.”
We know Firebase's real-time data synchronization will be a great fit with Ember's opinionated front-end philosophy, so let's take a look at how it all works.
To begin using EmberFire, simply include the necessary libraries in your application. Note that our EmberFire bindings work only with Ember Data.
<!-- Don't forget to include Ember and its dependencies --> <script src="http://builds.emberjs.com/canary/ember-data.js"></script> <script src="https://cdn.firebase.com/js/client/1.0.17/firebase.js"></script> <script src="emberfire.js"></script>
Now you're ready to automatically sync your Ember models with data stored in Firebase. To start using EmberFire, simply create an instance of DS.FirebaseAdapter and DS.FirebaseSerializer in your app, like this:
DS.FirebaseAdapter
DS.FirebaseSerializer
App.ApplicationAdapter = DS.FirebaseAdapter.extend({ firebase: new Firebase('https://<my-firebase>.firebaseio.com') }); App.ApplicationSerializer = DS.FirebaseSerializer.extend();
With the adapter and serializer set up, you can now interact with the data store as you normally would with Ember. For example, calling find() with a specific ID will retrieve that record from Firebase. It will also start watching for updates and will update the data store automatically whenever anything is added or removed.
For more documentation, check out the EmberFire README.
Yehuda, Tom, and the rest of the Ember Core Team, who gave us invaluable input on multiple iterations of the bindings. We’d also like to thank everyone who helped us test early versions of EmberFire and gave excellent feedback to help us make the bindings top-notch.
This is just the beginning of our Ember integration, so we welcome your feedback and participation! Submit a pull request, or share your thoughts on Twitter or the Firebase Google Group. We can't wait to see what you build with Ember and Firebase.
Update (November 4, 2014): While this post still contains some useful and relevant information, we have released advanced query functionality which solves a lot of the problems this post discusses. You can read more about it in our queries blog post.
No WHERE clauses? No JOIN statements? No problem!
Coming from a SQL background as I did, it can take a while to grok the freedom of NoSQL data structures and the simplicity of Firebase's dynamic, real-time query environment.
Part 1 of this double-header will will cover some of the common queries we know and love and talk about how they can be converted to Firebase queries. Part 2 goes on to cover some advanced query techniques for Firebase and a solution for full-blown content searches.
So let's jump in. Here's what we're going to cover today:
This article relies heavily on the proper use of the following API calls, all of which are introduced in the documentation for Queries and Limiting Data:
This article also leans heavily on theory from Anant's authoritative post, Denormalizing is Normal. Where Anant's post covers a wide breadth and highly foundational concepts, this post serves more as a quick reference and recipe book.
We're going to work with the examples-sql-queries Firebase for all the examples. Feel free to browse the data and get a feel for the structure.
A lot of times in our docs, you'll see something like var ref = new Firebase(URL); and then later, ref.child('user/1'). But in our examples we use new Firebase('URL/child/path'). So which should you use? They are functionally equivalent; use the one that keeps your code simple to read and maintain.
var ref = new Firebase(URL);
ref.child('user/1')
new Firebase('URL/child/path')
Using a variable will be a bit DRYer if it's going to be referenced several times, but creating multiple Firebase instances does not incur any additional overhead as this is all optimized internally by the SDK.
Select a user by ID (WHERE id = x)
We'll start off with the basics and build from here. In Firebase queries, records are stored in a "path", which is simply a URL in the data hierarchy. In our sample data, we've stored our users at /user. So to retrieve record by it's id, we just append it to the URL:
new Firebase('https://example-data-sql.firebaseio.com/user/1').once('value', function(snap) { console.log('I fetched a user!', snap.val()); });
See it work
Find a user by email address (WHERE email = x)
Selecting an ID is all good and fine. But what if I want to look up an account by something that's not already part of the URL path?
Well this is where ordered data becomes our friend. Since we know that email addresses will be a common lookup method, we can call setPriority() whenever we add a new record. Then we can use that priority to look them up later.
new Firebase("https://examples-sql-queries.firebaseio.com/user") .startAt('kato@firebase.com') .endAt('kato@firebase.com') .once('value', function(snap) { console.log('accounts matching email address', snap.val()) });
Pretty cool and useful for most cases, but what if we can't use priorities? Or we need to search on more than one field? Well then it's time to employ some indices!
See this example, which uses index/ to link email addresses to user accounts.
Get messages posted yesterday (WHERE timestamp BETWEEN x AND y)
What if we'd like to select a range of data? Ordering data with priorities is quite useful for this as well:
new Firebase("https://examples-sql-queries.firebaseio.com/messages") .startAt(startTime) .endAt(endTime) .once('value', function(snap) { console.log('messages in range', snap.val()); });
Paginate through widgets (LIMIT 10 OFFSET 10)
First of all, let's make some assertions. Unless we're talking about a static data set, pagination behavior becomes very ambiguous. For instance, how do I define page numbers in a constantly changing data set where records are deleted or added frequently? How do I define the offset? The "last" page? If those questions are difficult to answer, then pagination is probably not the right answer for your use case.
Pagination for small, static data sets (less than 1MB) can be done entirely client side. For larger static data sets, things get a bit more challenging. Assuming we're writing append-only data, we can use our ordered data examples above and assign each message a page number or a unique incremental counter and then use startAt()/endAt().
// fetch page 2 of messages new Firebase("https://examples-sql-queries.firebaseio.com/messages") .startAt(2) // assumes the priority is the page number .endAt(2) .once('value', function(snap) { console.log('messages in range', snap.val()); });
But what if we're working with something like our widgets path, which doesn't have priorities? We can simply "start at" the last record on the previous page by passing null for a priority, followed by the last record id:
// fetch page 2 of widgets new Firebase("https://examples-sql-queries.firebaseio.com/widget") .startAt(null, lastWidgetOnPrevPage) .limitToFirst(LIMIT+1) // add one to limit to account for lastWidgetOnPrevPage .once('value', function(snap) { var vals = snap.val()||{}; delete vals[lastWidgetOnPrevPage]; // delete the extraneous record console.log('widgets on this page', vals); });
See it work or see a full pagination example
Join records using an id (FROM table1 JOIN table2 USING id)
Firebasers talk a lot about denormalization, which is great advice, but how do you put things back together once you've split them apart? Well, it's a great deal simpler than it might seem.
Firebase is a real-time sync platform. It's built for speed and efficiency. You don't need to worry about creating extra references, and can listen to as many paths as you'd like to retrieve your data:
var fb = new Firebase("https://examples-sql-queries.firebaseio.com/"); fb.child('user/123').once('value', function(userSnap) { fb.child('media/123').once('value', function(mediaSnap) { // extend function: https://gist.github.com/katowulf/6598238 console.log( extend({}, userSnap.val(), mediaSnap.val()) ); }); });
If you are anything like me, your perfectionist instincts will be kicking in about now, since our merge logic synchronously waits for the user data to load before grabbing media. It also gets a bit verbose if we add several paths to be joined. So let's expand this into a utility that will merge any number of paths asynchronously, and stick a fork in it:
See three paths merged in parallel
More Tools to Come
Part 2 of this post covers some advanced techniques for performing content searching (e.g. WHERE description IS LIKE '%foo%').
WHERE description IS LIKE '%foo%'
We're hard at work optimizing Firebase's search and querying features by combining the best aspects of patterns like map reduce with the simplicity and speed of our real-time tools. Look for more news on this in the next few months! In the mean time, we'd love to hear your feedback. Let us know in the comments or email firebase-support@google.com.
Update (June 23, 2014): This post is about GeoFire 1.0. We have since released GeoFire 2.0 which can be read about in a blog post here.
Location based services are hot. From a black car to the best sushi near you, your favorite apps use location tracking and localized search to bring the (relevant) world to your fingertips. Today we’re excited to release GeoFire - a geospatial library for Firebase that makes it easy for you to add real-time, location-based features to your app; with just a few GeoFire function calls, you can leverage your users’ locations too.
The What
Imagine you want to build an app to monitor airplanes in and around the San Francisco airport - the app finds planes entering and leaving the airport’s airspace, which is defined by a preset radius around the control tower, and keeps track of all planes that are within the airspace.
The app needs two location-based features:
Localized search, to find planes that are located within the preset radius from the tower. As planes enter and leave the airspace, the search results must change too. Real-time location tracking, to ensure plane locations in the app are up-to-date.
Implementing these features using just Firebase is non-trivial: you have to store the planes’ locations, represented as latitude-longitude pairs, implement distance calculation with latitude-longitude pairs, implement efficient location modification and re-calculation of distances, …and ten other things you’d rather not; so we built GeoFire for you.
The How
The GeoFire library provides functions to store data that you’d like to query by location, update location data, and perform location based searches that reflect location updates in real-time.
With Firebase and GeoFire, all you need to do to build the control tower app is:
Call GeoFire’s insertByLocWithId function to store the planes’ initial locations to Firebase. Call the updateLocForId function to update the planes’ locations as they move. Call the onPointsNearLoc function with the tower’s location and airspace radius to get the set of planes within the airspace. Any changes to the set are automatically reported in real-time, so the function need only be called once.
insertByLocWithId
updateLocForId
onPointsNearLoc
And that’s it, you’re done. Yup, that’s what we said - location-based anything is easy with GeoFire.
The But Really How
To store data by location in Firebase, GeoFire converts the latitude-longitude pair to a geohash. A geohash is an alphanumeric string which is generated by interleaving the bit representations of the latitude and the longitude coordinates of a location, and base32-encoding the result. Geohashes have a neat property that makes them suitable for geospatial queries like localized search: points with similar geohashes are guaranteed to be near each other. (It's worth noting that points that are near each other may not have similar geohashes though.)
The data for location querying is stored at its geohash in Firebase. To search for the nearest data points to a given location, GeoFire executes a series of a prefix queries over the geohashes; the set of prefixes queried depends on the search radius and the zoom resolution. This blog post provides a good introduction to the terminology and geohashing.
For location queries by client-provided id, GeoFire also inserts data at its id in Firebase. A simple double look-up or look-up+operation is all that’s needed to support the x-by-Id functions then.
The Fun and Profit
GeoFire makes it delightfully easy for you to add location-based features to your app. So go forth, treat your users to some of that localized love we all love!
As always, we’re all ears for feedback, comments and questions - reach out to us via the Firebase google group or Twitter!
We first told the world about Firebase on April 12th, 2012.
We were blown away with the response: 35,000 unique visitors and 3,000 developer sign-ups in the first 24 hours.
The enthusiasm hasn’t waned over the past 16 months and this momentum has brought us to today’s big announcement. We’re taking the ‘beta’ label off and are officially launching Firebase!
This means two things: (1) Firebase is ready for large, mission-critical applications and (2) Firebase is now a paid product.
We operate at serious scale. Firebase is trusted by some of the biggest companies, and every day we see large, polished apps being launched on Firebase. Here are some highlights:
To ensure the long-term sustainability of Firebase, we’re now a paid product. We have a large free tier that is great for development and small production apps. Our paid tiers start at $49 / mo, and we have Enterprise pricing available for apps that need to scale to millions of concurrent users and terabytes of data. You can see our pricing here.
Thank you!
We want to thank you all for your help. Developer feedback is the reason the Firebase API is so concise and powerful. Your support requests, one-on-one feedback at hackathons, and honest critiques have helped us immensely. Please keep giving us your feedback.
Here are some of the highlights from our time in beta:
Our progress so far has been made with a small and talented team. Now that we’ve raised our Series A we’ll be expanding our team to better serve if you. If you’re an exceptional engineer, designer, or community manager you should join us!
The Future
We’re building the easiest-to-use platform for building rich, modern applications. We get up every morning because we’re passionate about helping developers create extraordinary experiences for their users, and we’re just getting started on this mission. You’ll see some great new features coming soon, including advanced querying, expanded platform support, offline disk persistence, and improved debugging and analytics tools.
Thank you again for your support. Keep building amazing things!
We’re happy to announce a new feature that gives you more power and flexibility when writing your Security and Firebase Rules! Specifically, we’re expanding the operations you can use for validating string data. You can now use .length, .contains(), .beginsWith(), .endsWith(), .replace(), .toLowerCase(), and .toUpperCase() to examine and manipulate strings. Here are a few examples of what you can do with the new operations:
.length
.contains()
.beginsWith()
.endsWith()
.replace()
.toLowerCase()
.toUpperCase()
Ensure only a string of at least 10 characters can be written:
".validate": "newData.isString() && newData.val().length >= 10"
Allow read access only if auth.identifier ends in @company.com:
".read": "auth.identifier.endsWith('@company.com')”
Normalize an email address and check for its existence under /users/:
".read": "root.child('users').child(auth.email.replace('.', ',').toLowerCase()).exists()"
For full details and more examples, see the documentation. And let us know what you’d like to see next!