« Writing an AngularJS App with an Express + Node.js Backend

[2012.06.20]

AngularJS is like the missing Batarang on your utility belt of web development awesomeness. It gives you two-way data binding that's both easy to use and fast, a powerful directive system that lets you use create reusable custom components, plus a lot more. Express is an excellent webserver for Node.js that provides routing, middleware, and sessions. Incidentally, the two work quite well together!

In this tutorial, I'm going to walk through writing a simple blog app with Angular and Express. For this, I'm assuming basic knowledge of Angular and Node. If you're new to Angular, I highly recommend that you check out the AngularJS homepage first.

If you'd rather skip to the end and see the finished product, you can grab the finished product from Github, or take a look at a live demo here.

Anatomy of the App

This application is really divided into two parts: client, and server. The client runs on the web browser, and does asynchronous communication to the server. The server then retrieves and stores data for the client. For this app, we'll use AngularJS to build the client and Express to build the server.

Getting the Angular Express Seed

To kick start the process of writing an AngularJS app, I've created the Angular Express Seed, based on the Express web server (which runs on Node.js) and the Angular Seed.

To get started, you can either clone the angular-node-seed repo from Github:

λ → git clone git://github.com/btford/angular-express-seed my-project

or download it as a zip.

Once you have the seed, you need to grab a few dependencies with npm. Open a terminal to the directory with the seed, and run:

λ → npm install

With these dependencies installed, you can run the skeleton app via:

λ → node app.js

and pointing your browser to http://localhost:3000.

Building the AngularJS client

We'll start by defining some views and client-side behavior, then writing a JSON API that our AngularJS client can interact with.

First, we're going to write some views using Jade. Jade is a template language that integrates well with Express. It lets you write HTML more concisely.

For example, this Jade:

doctype 5
html(lang="en")
  head
    title= pageTitle
  body
    h1 Jade - node template engine
    #container
      p This is a paragraph

Will compile to this HTML:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Jade</title>
  </head>
  <body>
    <h1>Jade - node template engine</h1>
    <div id="container">
      <p>This is a paragraph</p>
    </div>
  </body>
</html>

An important note here is that while Jade lets you do interpolation via #\{someVar\}, we're going to reserve interpolation for AngularJS to take care of on the client so can take advantage of the two-way data binding that Angular provides. Nevertheless, Jade is an awesome HTML preprocessor, and takes a lot of the typing out of writing views.

Open views/index.jade and replace the contents with:

extends layout

block body
  div
    h2 My Blog
    ul
      li
        a(href='/') Home
      li
        a(href='/addPost') Add a new post

    div(ng-view)

  script(src='js/lib/angular/angular.js')
  script(src='js/app.js')
  script(src='js/services.js')
  script(src='js/controllers.js')
  script(src='js/filters.js')
  script(src='js/directives.js')

The first few lines tell Express to wrap the boilerplate template in views/layout.jade around our view. Then we have a header, followed by a navigation bar, and finally a placeholder for Angular views.

Next, we'll add views for each of the basic functions: reading a list of short summaries of posts, reading a full-length post, editing a post, or deleting a post. Create a new file, views/partials/index.jade:

p There are {{posts.length}} posts
div(ng-repeat='post in posts')
  h3 {{post.title}}
  div {{post.text}}
  a(href='readPost/{{post.id}}') More
  |  - 
  a(href='editPost/{{post.id}}') Edit
  |  - 
  a(href='deletePost/{{post.id}}') Delete

This is a pretty straightforward view: it displays the number of posts at the top, then iterates through each post creating a div for each one, with links to read the full post, edit it, or delete it.

Let's create a few more views. The first is for reading a full post on its own page: views/partials/readPost.jade

h3 {{post.title}}
div {{post.text}}

One for adding a new post: views/partials/addPost.jade

h2 Write a new post
label(for='title') Title: 
input(ng-model='form.title', name='title')
p Body:
textarea(ng-model='form.text', cols='50', rows='15')
p
  button(ng-click='submitPost()') Submit Post

One for editing an existing post: views/partials/editPost.jade

h2 Edit post
label(for='title') Title: 
input(ng-model='form.title', name='title')
p Body:
textarea(ng-model='form.text', cols='50', rows='15')
p
  button(ng-click='editPost()') Edit Post

Lastly, a view for deleting a post: views/partials/deletePost.jade

h2 {{post.title}}
div {{post.text}}
p
  | Are you sure you want to delete this post? 
  button(ng-click='deletePost()') Yes
  |  - 
  button(ng-click='home()') No thanks

Now we need to connect these views to controllers. Open public/js/app.js and modify it accordingly:

// Declare app level module which depends on filters, and services
angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives']).
  config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
    $routeProvider.
      when('/', {
        templateUrl: 'partials/index',
        controller: IndexCtrl
      }).
      when('/addPost', {
        templateUrl: 'partials/addPost',
        controller: AddPostCtrl
      }).
      when('/readPost/:id', {
        templateUrl: 'partials/readPost',
        controller: ReadPostCtrl
      }).
      when('/editPost/:id', {
        templateUrl: 'partials/editPost',
        controller: EditPostCtrl
      }).
      when('/deletePost/:id', {
        templateUrl: 'partials/deletePost',
        controller: DeletePostCtrl
      }).
      otherwise({
        redirectTo: '/'
      });
    $locationProvider.html5Mode(true);
  }]);

This routing configuration will also let the app use the HTML5 push/pop history API.

We're almost done with the client side of the app. The last thing we need is the set of controllers to make requests to our server. Open public/js/controllers.js:

function IndexCtrl($scope, $http) {
  $http.get('/api/posts').
    success(function(data, status, headers, config) {
      $scope.posts = data.posts;
    });
}

function AddPostCtrl($scope, $http, $location) {
  $scope.form = {};
  $scope.submitPost = function () {
    $http.post('/api/post', $scope.form).
      success(function(data) {
        $location.path('/');
      });
  };
}

function ReadPostCtrl($scope, $http, $routeParams) {
  $http.get('/api/post/' + $routeParams.id).
    success(function(data) {
      $scope.post = data.post;
    });
}

function EditPostCtrl($scope, $http, $location, $routeParams) {
  $scope.form = {};
  $http.get('/api/post/' + $routeParams.id).
    success(function(data) {
      $scope.form = data.post;
    });

  $scope.editPost = function () {
    $http.put('/api/post/' + $routeParams.id, $scope.form).
      success(function(data) {
        $location.url('/readPost/' + $routeParams.id);
      });
  };
}

function DeletePostCtrl($scope, $http, $location, $routeParams) {
  $http.get('/api/post/' + $routeParams.id).
    success(function(data) {
      $scope.post = data.post;
    });

  $scope.deletePost = function () {
    $http.delete('/api/post/' + $routeParams.id).
      success(function(data) {
        $location.url('/');
      });
  };

  $scope.home = function () {
    $location.url('/');
  };
}

The controllers are all relatively straightforward: they make calls to $http to communicate with the server, and attach methods to the controller's scope to allow the corresponding view to make additional requests on a button click.

Let's look at EditPostCtrl alongside its view, views/partials/editPost.jade for example:

function EditPostCtrl($scope, $http, $location, $routeParams) {
  $scope.form = {};
  $http.get('/api/post/' + $routeParams.id).
    success(function(data) {
      $scope.form = data.post;
    });

  $scope.editPost = function () {
    $http.put('/api/post/' + $routeParams.id, $scope.form).
      success(function(data) {
        $location.url('/readPost/' + $routeParams.id);
      });
  };
}
h2 Edit post
label(for='title') Title: 
input(ng-model='form.title', name='title')
p Body:
textarea(ng-model='form.text', cols='50', rows='15')
p
  button(ng-click='editPost()') Edit Post

When the controller loads, it makes a request to /api/post/{{$routeParams.id}}, which it uses to populate $scope.title and $scope.text. Then, it attaches a method editPost to the local scope, which sends a POST request to the server which contains the id of the post to edit and the updated title and text. In editPost.jade, the input and textarea have ng-model attributes that setup data binding, and we attach $scope.editPost() to a button so that it fires when the button is pressed.

Building the JSON server with Express

Typically, you'd connect to some database and store/load your data from there, however for this example app I'm just going to use an in-memory object.

Express has an excellent way to respond with JSON rather than HTML through res.json).

Open routes/api.js and replace the contents with:

// initialize our faux database
var data = {
  "posts": [
    {
      "title": "Lorem ipsum",
      "text": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
    },
    {
      "title": "Sed egestas",
      "text": "Sed egestas, ante et vulputate volutpat, eros pede semper est, vitae luctus metus libero eu augue. Morbi purus libero, faucibus adipiscing, commodo quis, gravida id, est. Sed lectus."
    }
  ]
};

// GET

exports.posts = function (req, res) {
  var posts = [];
  data.posts.forEach(function (post, i) {
    posts.push({
      id: i,
      title: post.title,
      text: post.text.substr(0, 50) + '...'
    });
  });
  res.json({
    posts: posts
  });
};

exports.post = function (req, res) {
  var id = req.params.id;
  if (id >= 0 && id < data.posts.length) {
    res.json({
      post: data.posts[id]
    });
  } else {
    res.json(false);
  }
};

We'll continue adding code to this file to handle adding, editing, and deleting posts:

// POST
exports.addPost = function (req, res) {
  data.posts.push(req.body);
  res.json(req.body);
};

// PUT
exports.editPost = function (req, res) {
  var id = req.params.id;

  if (id >= 0 && id < data.posts.length) {
    data.posts[id] = req.body;
    res.json(true);
  } else {
    res.json(false);
  }
};

// DELETE
exports.deletePost = function (req, res) {
  var id = req.params.id;

  if (id >= 0 && id < data.posts.length) {
    data.posts.splice(id, 1);
    res.json(true);
  } else {
    res.json(false);
  }
};

Next, we have to connect these routes to the appropriate requests. Open app.js, and below the app configuration, add this code under the //Routes comment:

/** app init & config... */

// Routes

app.get('/', routes.index);
app.get('/partials/:name', routes.partials);

// JSON API

app.get('/api/posts', api.posts);

app.get('/api/post/:id', api.post);
app.post('/api/post', api.addPost);
app.put('/api/post/:id', api.editPost);
app.delete('/api/post/:id', api.deletePost);

/* ... */

The first route takes care of serving our main page that runs the angular app. The next route lets Angular load partials on demand. The following routes establish our JSON API, linking http://yourapp.com/api/posts to the posts route, http://yourapp.com/api/addPost to the addPost route, and so on.

Notice the :id on lines 12, 14, and 15: that's a parameter parsed out of the URL by Express. Why doesn't app.post('/api/post', api.addPost); on line 13 have that parameter, then? The reason is because it's better to do a POST rather than a GET to avoid cross-site scripting. In general, when requesting information, use GET, but when invoking some action, use POST.

Start up the app as we did with the skeleton earlier:

λ → node app.js

You should be able to add, update, read, and delete posts.

Conclusion

Writing apps with AngularJS and Express is both fun and easy. Both frameworks let you dive right into the business logic of your app without much boilerplate, so in just a few hundred lines, we're able to write a complete (albeit simple) app. Maintaining a clear distinction between client and server makes the app easier to maintain and test. In a future post, I'll cover how to test an app built on Angular and Express.

There are a lot of other possible improvements to this blog. You'll notice some flickering between loads. AngularJS provides ways to address this issue, but that's outside of the scope of this post. A real blog application would also probably want posts to have some notion of formatting, which is also achievable using some of Angular's more advanced features. I also hope to address these things in subsequent posts.

Comments? Tweet or email me. Corrections? Send me a pull request on Github.

Questions

[2012.09.17] - @corydeppen asks: "Did you use $http instead of $resource for a specific reason? Are there cases where you would recommend one over the other?"

I certainly could have used $resource, but I chose to use $http in the tutorial because I thought it was the simpler of the two and would better help illustrate how all of the parts connected. In choosing which to use in your own app, I think it kind of depends your needs. If you are serializing/deserializing objects into/from a data store and doing CRUD-y things, $resource would likely be the way to go. If you have a more action/intent based API scheme, you might want to use $http instead. And you can certainly use both within the same app if it makes sense to you!