Backend Integration with Node Express

In this chapter we will have a look into solving common problems when combining Angular.js with the Node.js Express framework. The examples used in this chapter are based on a Contacts app to manage a list of contacts. As an extra we use MongoDB as a backend for our contacts since it requires further customization to make it work in conjunction with Angular’s $resource service.

Consuming REST APIs

Problem

You wish to consume a JSON REST API implemented in your Express application.

Solution

Using the $resource service we will begin by defining our Contact model and all RESTful actions.

 1 app.factory("Contact", function($resource) {
 2   return $resource("/api/contacts/:id", { id: "@_id" },
 3     {
 4       'create':  { method: 'POST' },
 5       'index':   { method: 'GET', isArray: true },
 6       'show':    { method: 'GET', isArray: false },
 7       'update':  { method: 'PUT' },
 8       'destroy': { method: 'DELETE' }
 9     }
10   );
11 });

We can now fetch a list of contacts using Contact.index() and a single contact with Contact.show(id). These actions can be directly mapped to the API routes defined in app.js.

 1 var express = require('express'),
 2         api = require('./routes/api');
 3 
 4 var app = module.exports = express();
 5 
 6 app.get('/api/contacts', api.contacts);
 7 app.get('/api/contacts/:id', api.contact);
 8 app.post('/api/contacts', api.createContact);
 9 app.put('/api/contacts/:id', api.updateContact);
10 app.delete('/api/contacts/:id', api.destroyContact);

I like to keep routes in a separate file routes/api.js and just reference them in app.js in order to keep it small. The API implementation first initializes the Mongoose library and defines a schema for our Contact model.

1 var mongoose = require('mongoose');
2 mongoose.connect('mongodb://localhost/contacts_database');
3 
4 var contactSchema = mongoose.Schema({
5   firstname: 'string', lastname: 'string', age: 'number'
6 });
7 var Contact = mongoose.model('Contact', contactSchema);

We can now use the Contact model to implement the API. Lets start with the index action:

1 exports.contacts = function(req, res) {
2   Contact.find({}, function(err, obj) {
3     res.json(obj)
4   });
5 };

Skipping the error handling we retrieve all contacts with the find function provided by Mongoose and render the result in the JSON format. The show action is pretty similar except it uses findOne and the id from the URL parameter to retrieve a single contact.

1 exports.contact = function(req, res) {
2   Contact.findOne({ _id: req.params.id }, function(err, obj) {
3     res.json(obj);
4   });
5 };

As a final example we will create a new Contact instance passing in the request body and call the save method to persist it:

1 exports.createContact = function(req, res) {
2   var contact = new Contact(req.body);
3   contact.save();
4   res.json(req.body);
5 };

You can find the complete example on github.

Discussion

Let have a look again at the example for the contact function, which retrieves a single Contact. It uses _id instead of id as the parameter for the findOne function. This underscore is intentional and used by MongoDB for its auto-generated IDs. In order to automatically map from id to the _id parameter we used a nice trick of the $resource service. Take a look at the second parameter of the Contact $resource definition: { id: "@_id" }. Using this parameter Angular will automatically set the URL parameter id based on the value of the model attribute _id.

Implementing Client-Side Routing

Problem

You wish to use client-side routing in conjunction with an Express backend.

Solution

Every request to the backend should initially render the complete layout in order to load our Angular app. Only then will the client-side rendering take over. Let us first have a look at the route definition for this “catch all” route in our app.js.

1 var express = require('express'),
2      routes = require('./routes');
3 
4 app.get('/', routes.index);
5 app.get('*', routes.index);

It uses the wildcard character to catch all requests in order to get processed with the routes.index module. Additionally, it defines the route to use the same module. The module again resides in routes/index.js.

1 exports.index = function(req, res){
2   res.render('layout');
3 };

The implementation only renders the layout template. It uses the Jade template engine.

 1 !!!
 2 html(ng-app="myApp")
 3   head
 4     meta(charset='utf8')
 5     title Angular Express Seed App
 6     link(rel='stylesheet', href='/css/bootstrap.css')
 7   body
 8     div
 9       ng-view
10 
11     script(src='js/lib/angular/angular.js')
12     script(src='js/lib/angular/angular-resource.js')
13     script(src='js/app.js')
14     script(src='js/services.js')
15     script(src='js/controllers.js')

Now that we can actually render the initial layout we can get started with the client-side routing definition in app.js

 1 var app = angular.module('myApp', ["ngResource"]).
 2   config(['$routeProvider', '$locationProvider',
 3     function($routeProvider, $locationProvider) {
 4       $locationProvider.html5Mode(true);
 5       $routeProvider
 6         .when("/contacts", {
 7           templateUrl: "partials/index.jade",
 8           controller: "ContactsIndexCtrl" })
 9         .when("/contacts/new", {
10           templateUrl: "partials/edit.jade",
11           controller: "ContactsEditCtrl" })
12         .when("/contacts/:id", {
13           templateUrl: "partials/show.jade",
14           controller: "ContactsShowCtrl" })
15         .when("/contacts/:id/edit", {
16           templateUrl: "partials/edit.jade",
17           controller: "ContactsEditCtrl" })
18         .otherwise({ redirectTo: "/contacts" });
19     }
20   ]
21 );

We define route definitions to list, show and edit contacts and use a set of partials and corresponding controllers. In order for the partials to get loaded correctly we need to add another express route in the backend which serves all these partials.

1 app.get('/partials/:name', function (req, res) {
2   var name = req.params.name;
3   res.render('partials/' + name);
4 });

It uses the name of the partial as an URL param and renders the partial with the given name from the partial directory. Keep in mind that you must define that route before the catch all route, otherwise it will not work.

You can find the complete example on github.

Discussion

Compared to Rails the handling of partials is quite explicit by defining a route for partials. On the other hand it is quite nice to being able to use jade templates for our partials too.