Firebase is a backend as a service provider. It can store data and authenticate your users with just a few lines of code. It integrates well with many different frameworks, including Ember.js.
In this recipe, we'll take a look at a few of the features of Firebase by creating a blogging application. This app will allow users to create, edit, or delete posts. Users will be able to log in with Twitter and be authenticated as well.
Before getting started, we must set up an account with Firebase at http://www.firebase.com. Google owns Firebase so this should be really easy. In fact, you should be able to log in directly with your Google credentials.
After logging in, you'll need to create a new application and set up a new provider. Follow these steps:
To get these keys, you'll need to create a new application through Twitter. To do this, go to apps.twitter.com
and click on CREATE NEW APP. Follow the onscreen instructions. Make sure to set the callback URL to https://auth.firebase.com/auth/twitter/callback
.
This should be it. Make sure that you write down the name of the URL that Firebase created. You'll need it later when you set everything up in the environment.js
file.
$ ember install emberfire $ ember install torii $ ember install ember-bootstrap $ ember install ember-cli-showdown $ ember g route new $ ember g route posts $ ember g route application $ ember g controller new $ ember g controller posts $ ember g model post title:string body:string author:string titleURL:string $ ember g template index $ ember g util clean
The files generated will be the skeleton of our application. The two install
commands will install the necessary files for Firebase with authentication using an add-on called torii
.
// app/adapters/application.js import Ember from 'ember'; import FirebaseAdapter from 'emberfire/adapters/firebase'; const { inject } = Ember; export default FirebaseAdapter.extend({ firebase: inject.service(), });
This adapter is automatically generated for us when we install the emberfire
add-on. It injects the Firebase service into our application and data store.
emberfire
adapter by editing the environment.js
file:// config/environment.js … firebase: 'https://testemberfire.firebaseio.com/', torii: { sessionServiceName: 'session' }, …
To use Firebase, you must set the firebase
property to your Firebase URL that you created earlier. Make sure that the torii
property is also set so that we can use the session
object in our application.
torii-adapters
and add the application.js
file:// app/tori-adapters/application.js import Ember from 'ember'; import ToriiFirebaseAdapter from 'emberfire/torii-adapters/firebase'; export default ToriiFirebaseAdapter.extend({ firebase: Ember.inject.service() });
Torii is a type of authentication abstraction for Ember.js. This will make it possible to use the session variable in our program.
clean.j
s file in the utils
folder:// app/utils/clean.js export default function clean(title) { title = title.replace(/ /g, '_'); return title.replace(/[^a-zA-Z0-9-_]/g, ''); }
This file is simply used to clean a URL and return it. In other words, it removes anything other than dashes and the characters, a-z
. We'll be using this later for our URLs.
// app/models/post.js import DS from 'ember-data'; export default DS.Model.extend({ title: DS.attr('string'), body: DS.attr('string'), author: DS.attr('string'), titleURL: DS.attr('string') });
The model contains all the data we'll be using for each post in our application. This should have been generated earlier.
router.js
file using the titleURL
as the path:// app/router.js import Ember from 'ember'; import config from './config/environment'; const Router = Ember.Router.extend({ location: config.locationType }); Router.map(function() { this.route('posts', {path: '/:titleURL'}, function() { }); this.route('new'); }); export default Router;
Some of this was generated for us when we created the posts and new route. However, we want to make sure that titleURL
is set to the path of each individual post. We do this by passing the :titleURL
dynamic segment to the path.
// app/routes/posts.js import Ember from 'ember'; export default Ember.Route.extend({ model(param) { return this.store.query('post', { orderBy: 'titleURL', equalTo: param.titleURL }); } });
When the user navigates to the /posts
URL, the model will expect a parameter passed in. For example, if you navigate to /posts/my_post
, the my_post
segment will be passed as a parameter that can be accessed in the route. We'll use this parameter in the Firebase this.store.query
method. The first argument is the name of the model. We can then use orderBy
and equalTo
to specify the exact post that we are looking for.
Uniqueness
As you can imagine, when creating a new post, the title may or may not be unique. The this.store.query
method will return all results as an array to the model. We could enforce uniqueness in Firebase by making the titleURL
unique. Another possibility would be to check the uniqueness of the post title during creation. Either way, for this example, we'll assume that all titleURLs are unique.
// app/routes/application.js import Ember from 'ember'; export default Ember.Route.extend({ model(){ return this.store.findAll('post'); }, actions:{ login(){ this.get('session').open('firebase', { provider: 'twitter'}).then((data)=> { }); }, logout(){ this.get('session').close(); } } });
We want the main application to have access to the model route so that we can use the findAll
method to retrieve all posts. This is basically the same as the Ember Data method we've used in previous recipes.
There are two actions, login
and logout
. As we injected, using torii
, our session into the program, we can access it from anywhere. By invoking this.get('session')
, we can open
or close
a session. Firebase has several built-in authenticators, including Twitter and Facebook. The login
action in this example will open
a window to twitter
so that the user can be authenticated.
Firebase security
With any JavaScript browser application, security can be tricky. Firebase makes this a little easier for us. Firebase keeps track of users that are authenticated. In the Firebase dashboard, you can set rules that make it possible for only authenticated users to receive data. This is a little beyond the scope of this recipe. However, it is possible to secure your data with Firebase using a third-party authenticator such as Twitter or Facebook.
//app/templates/application.hbs <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header" href="#"> {{#link-to 'index' class='navbar-brand'}}My New Blog{{/link-to}} </div> <ul class="nav navbar-nav"> {{#if session.isAuthenticated}} <li>{{#link-to 'new'}}Add New Post{{/link-to}}</li> {{/if}} </ul> <ul class="nav navbar-nav navbar-right"> {{#unless session.isAuthenticated}} <li><a href="#" {{action 'login' }}>Login</a></li> {{else}} <li><a href="#" {{action 'logout' }}>Logout</a></li> {{/unless}} </ul> </div> </nav> <br> <br> <br> {{outlet}}
As we have installed the ember-bootstrap
add-on, we can create a really simple top navigation bar. The login
and logout
buttons are surrounded by the if
helper. In every template, you have access to the session
property. This property has a method called isAuthenticated
that returns true
if the user is logged in and false
if the user is not logged in. We can use this to show the login
button only if the user is NOT logged in. If the user is logged in, they'll see a logout
button.
We don't have an application controller, so these actions will bubble up to the application route where they'll be handled.
index.hbs
file with a link to each individual post:// app/templates/index.hbs <div class = 'row'> <div class='col-md-4'> <h1>Posts</h1> {{#each model as |post|}} <br>{{#link-to 'posts' post.titleURL}}{{post.title}}{{/link-to}} {{/each}} </div> </div>
The model
loops through each post and displays the title
on the screen. Each titleURL
is passed as a parameter to the posts route.
// app/templates/new.hbs <br><br> <div class='col-md-4 border' > <h1>New Post</h1> <form {{action 'save' on="submit"}}> <dl> <dt>Title:<br> {{textarea value=title cols="40" |rows="1" placeholder='Title'}}</dt> <dt>Body:<br> {{textarea value=body cols="40" rows="6" placeholder='Body'}}</dt> </dl> <button type='submit' class='btn btn-primary'>Add</button> </form> </div> <div class='col-md-4 border' > <h1>Preview</h1> <h3>{{title}}</h3> <h4>{{markdown-to-html markdown=body}}</h4> </div>
The new template will be used to create a new post. The textarea
helper creates two textboxes. The form has a save
action that will be triggered when the form is submitted.
When setting up the project, we installed a markdown
add-on. This allows us to use markdown
in the body of the post. Markdown is a text-to-HTML conversion tool. It makes it easier to write HTML in your text.
// app/templates/posts.js {{#each model as |model|}} <div class='row'> {{#if isEditing}} <div class='col-md-4 border'> <form {{action 'save' on='submit'}}> <dl> <dt>Title:<br> {{textarea value=model.title cols='40' rows='1'}}</dt> <dt>Body:<br> {{textarea value=model.body cols='40' rows='6'}}</dt> </dl> <div class = 'row'> <button type='submit' class = 'btn btn-primary'>Done</button> </div> </form> </div> {{/if}} <div class='col-md-4 border'> <h1>{{model.title}}</h1> <h3>{{markdown-to-html markdown=model.body}}</h3> <h4>-{{model.author}}</h4> {{#if session.isAuthenticated}} <form {{action 'edit' }}> <button type='submit' class='btn btn-primary'>Edit</button> <button type='delete' class= 'btn btn-primary' {{action 'delete'}}>Delete</button> </form> {{/if}} </div> </div> {{/each}}
This displays each individual post. If the user is authenticated, they can either delete or edit the post.
Once again, we use the textarea
template helpers to display the textboxes. The form has an edit action attached that will set the isEditing
property to true
so that the post can be edited. The delete
action deletes the post.
save
action to the new controller:// app/controllers/new.js import Ember from 'ember'; import cleanURI from '../utils/clean'; export default Ember.Controller.extend({ actions: { save(){ const titleURL= cleanURI(this.get('title')); const post = this.store.createRecord('post',{ title: this.get('title'), body: this.get('body'), author: 'test', titleURL: titleURL }); post.save(); this.set('title',''); this.set('body',''); this.transitionToRoute('index'); } } });
The save
action is used to save the data to Firebase. First, it takes the title of the post and uses the utility, cleanURI
, to remove all special characters and spaces. Firebase has a function called createRecord
that is used to create new records. We then save the record to the store and set
the values back to default. Finally, the application transitions back to the index.
edit
, delete
, and save
:// app/controllers/posts.js import Ember from 'ember'; import cleanURI from '../utils/clean'; export default Ember.Controller.extend({ actions: { edit(){ this.set('isEditing', true); }, delete(){ this.get('model').forEach(model=>{ model.deleteRecord(); }); this.get('model').save(); this.set('isEditing', false); this.transitionToRoute('index'); }, save(){ this.get('model').forEach(model=>{ const titleURL = cleanURI(model.get('title')); model.set('titleURL', titleURL); model.save(); }); this.set('isEditing',false); this.transitionToRoute('index'); } } });
Let's break this down into more detail:
… edit(){ this.set('isEditing',true); }, …
The edit function sets the isEditing
property to true
. The posts template uses this property to show or not show the editing window:
… delete(){ this.get('model').forEach(model=>{ model.deleteRecord(); }); this.get('model').save(); this.set('isEditing',false); this.transitionToRoute('index'); }, …
The delete
action deletes the record. To do this, we must use the forEach
method on our model
. In the route, we used the query
method, which returns an array. Therefore, we have to go through every record returned, and delete it. Once again, we'll assume that every title is unique and only has one record. Remember to always .save()
so that the record is persisted in Firebase. After the record is deleted, we transition to the index route:
… save(){ this.get('model').forEach(model=>{ const titleURL = cleanURI(model.get('title')); model.set('titleURL',titleURL); model.save(); }); this.set('isEditing',false); this.transitionToRoute('index'); } …
The save
function gets the title, cleans it, sets it, and saves the model. In this example, we must use the forEach
method to iterate over the array. Afterward, we set the isEditing
property back to false
and transition back to the index
.
This displays the top left corner of the screen. No posts are listed as we haven't added them yet:
From here, we can make any changes and save it back again. Each time a save occurs, the post is persisted in Firebase.
Firebase talks to its backend service via the emberfire
and torii
add-ons. EmberFire is an official Ember Data adapter. It has many of the same features as other popular adapters. It can save, delete, edit, and query data fairly easily. One of its purposes is to make it really easy to persist and save data without having to set up your own backend.
Firebase also has authentication providers that it can hook into. Firebase handles all the authentication between the provider and application. All that this requires is that the provider is set up in Firebase.