This is the fourth part in a series of blog posts about building a real-time SMS and voice voting application using Node.js. In part one, we created the Node.js application and captured incoming votes over SMS and stored them in a CouchDB. In part two, we created a real-time visualization of the voting using Socket.io and Highcharts. In part three, we tweaked our app to scale to thousands of votes per second and millions of total votes.
Through the first 3 parts of this series we now have a scalable voting application that can process votes for events via SMS or voice and display the real-time progress of voting. However, there is currently no web interface for an administrator. Creating events or modifying them require making changes directly to the documents in CouchDB. In this blog post, I will walk through the process of creating a simple web interface for administrators using AngularJS.
Why Angular JS?
There are quite a few client-side MVC frameworks out there that a developer can use to build web apps. This is both a blessing and a curse. On the one hand, competition between all of these frameworks for your attention ensures that they will get better and better. On the other hand, it can be bewildering trying to figure out which one is the best fit for you and your project.
That being said, I needed to choose one. I played around with Backbone and Ember but ultimately settled on Angular for the for the following reasons:
It builds on the declarative nature of HTML and CSS
It is under active development and is supported by a large, well-regarded tech company
It feels very much like where the web is going with Web Components
AngularJS + Node.js + CouchDB
In this blog post, we’re going to cover:
Creating the beginning of a simple AngularJS app
Routing web requests to it through Node.js
Authentication using CouchDB credentials
This is less of an exhaustive AngularJS 101 tutorial and more of a pragmatic walk-through on how to get AngularJS running, connect it to RESTful services being powered by Node.js and handle authentication using CouchDB. If that sounds good to you, let’s go!
Route Admin Requests to Angular JS
The first thing we need to do is take browser requests for
and route them to our AngularJS application. We’ll need to add a new Express route in app.js to handle this. In addition, we’re going to do a little bit of extra work to make sure that all requests are sent over HTTPS since we will be dealing with sensitive log-in credentials:
Now let’s add an
method to our routes module to render a template called
:
Create A View For The App
All requests for
now cause a template called
to get rendered. Let’s create a file called admin.hjs and place it in our views directory. Think of this file as a template that contains the frame for the web page (header, footer, etc) and a portion inside of the frame that AngularJS will populate with dynamic content.
As I mentioned, AngularJS builds on the declarative nature of HTML and CSS. There are several custom attributes that we will use to declare our application’s intentions. Let’s walk through admin.hjs and discuss what some of these declarations do:
ng-app='votr'>
The ng-app declaration tells AngularJS that this this element and everything inside of it are part of the AngularJS application called votr.
Here we include the core AngularJS library, the
library (more on that later) and our own
file which defines our application.
Lastly, we use the
declaration to specify that this is the
This declares our application, names it
and specifies that it will rely on the
module. Next, let’s define the routes that make up our admin application. In our application we’re only going to define three: logging-in, listing events and logging-out:
Remember, these are just the client-side routes for AngularJS. The server-side route for our Node.js application is /admin/. So the fully qualified URIs will look like this:
Login =
Event List =
Logout =
Create A Login Controller and View
Now that most of the bootstrapping is behind us, we can start to focus on the HTML and JS that will power our application. Let’s start by going back and reviewing our route declarations and look for the one corresponding to the path
:
So, the path
is associated with a template called login.html and a controller called
. Let’s check out our login template:
This is a pretty simple form. The input tags include ng-model directives for the username and password. This directive is used to bind the value of what you type into those fields with an object called user that has username and password attributes.
Attached to the form tag is an AngularJS directive called ng-submit that points to a function called
. Since our
path is associated with the
, you should expect a login function to be defined to handle the form submission, which it is:
Let’s Pause: $scope and Dependency Injection
Let’s not gloss over the parameters being passed-in to the LoginCtrl function. AngularJS makes extensive use of dependency injection. You’ll start to notice that none of the functions in an AngularJS application reference variables that aren’t passed-in to or declared within the function. No global variables are used.
There are numerous benefits to this approach that I don’t have time to cover here. From the perspective of a developer building an AngularJS application, please be aware that you’ll need to pass in any objects that your functions need to work properly. Some objects (such as $scope and $rootScope) don’t require any set-up on your part, they are pre-defined by the framework.
Other dependencies, such as the SessionService you see in LoginCtrl, will need to be defined and registered with AngularJS:
Finally, let’s cover
and
. The
object is a container for all functions and objects that are needed for a single controller. In the case of
,
contains the object user (which hold username and password data) and the login function (which logs a user in to the web app). The
object, as the name implies, is a container for values and functionality needed across all controllers. In our app we use
to store the logged-in state of the user.
Authenticate Using CouchDB and Cookies
Now, before we can show the user a list of events we need to ask them to log-in. Rather than create an additional username/password system, we’ll piggyback on CouchDB’s authentication scheme. After all, if you can log-in to CouchDB directly and edit the data, you’re probably allowed the log-in to the web application to do the same.
When a user submits their username and password on the login form, these credentials are passed all the way down and used to log-in to CouchDB. CouchDB returns a cookie to use for subsequent authenticated requests to the database. When the cookie expires (or hasn’t been issued yet) CouchDB will return an HTTP 401 error.
The AngularJS application interacts with the server (i.e. the fetch a list of events) via AJAX calls. Errors are passed back, so we can keep an eye out for 401 errors to let us know that the user needs to log-in. Below is how you configure AngularJS to intercept and inspect HTTP requests:
It’s worth going over that code again. The first block (request) covers all HTTP requests. The AngularJS application has a global variable (
) that we use to store the login state. If this variable is undefined or false, the user is redirected to the login view.
The second block (responseError) covers the event of an HTTP error happening during an AJAX call. If the error is 401 (not logged in) the user is redirected to the login page.
Build A RESTful Login API
Getting back to the login function, you’ll notice a call to
. SessionService is a service class. In AngularJS you specify the unique id of the service and the code for how it should be instantiated by the service factory.
In our case
is a thin wrapper around a
object. A $resource object provides a programmatic interface between your app and a RESTful web service. When you instantiate a
object, you specify the root endpoint for that RESTful service and AngularJS will convert your calls of get(), save(), etc into the equivalent RESTful HTTP requests to that endpoint.
In our case, when a user attempts to login, we execute save on the
. This initiates a POST to the API endpoint that we defined. Let’s walk all the way through this form submission to the log-in to CouchDB itself starting with app.js:
In the routes module we have a handle to the
module. This is the module that we use to house all functionality related to logging-in. We call the login method, passing in the username and password:
Inside of the sessions module, we have a handle to our CouchDB client and we attempt to authenticate:
If the login fails, we return the error. This errors bubbles its way back up and and HTTP error is returned to the client. The error handler of the call to
is triggered and we set $scope.loginError to true.
In the markup for login.html you’ll notice a
to true caused the div to display. If it was set to false or undefined, it would be hidden.
Now, if authentication succeeds, we are given a cookie that we can use to make subsequent requests. We store this cookie in memory on the server and associate it with the username of the person who logged-in. This enables us to delete the cookie from memory when the user logs out. The success of the log-in bubbles up through our Node.js application is returned to the client as an HTTP 200. We then forward the user to the Event List view.
Seeing This In Action
Fire up the node process and point your browser at http://localhost:3000/admin/. Initially, the root of our AngularJS application will load (
) but we will soon get re-directed to
:
At this point, go ahead and enter some bogus credentials. These will get passed all the way through to the CouchDB instance where authentication will fail and our server will return a 401 error. This will set
to true which will trigger the error message:
Finally, go ahead and authenticate using your valid CouchDB credentials. This should succeed and the
will then redirect to the root path (
) which will load the…
To Be Continued…
And that’s where we’re going to stop! We’ve covered a ton of ground in this blog post, let’s quickly recapped the things we’ve learned:
Setting up Node.js and Express to route requests to the AngularJS application
Creating the HTML page to house the AngularJS app
Defining the routes
Building the controller and associated view the login experience
Authentication using CouchDB and Cookies
Handling login with a RESTful API and the $http interceptor
All of the code we’ve gone over here is posted on Github. In the next blog post, we will finish building this AngularJS application and add all of the functionality necessary to load a list of events, edit an event, create new events and delete events.
Join the conversation on Reddit!
Votr Part 4: AngularJS and Authentication with CouchDB