Thursday, October 3, 2013

Building Sites Quickly with Node.js and AngularJS

There are increasingly many options for getting sites up and running quickly. This week I thought I'd combine two options that I have worked with previously, Node.js and AngularJS. Using my ec2 server I decided to work out a few demonstrations.

At its simplest, Node.js acts as an HTML module that can serve simple content to a caller and provide consistent connections for messaging or chatting. Through the power of its modules, it can be used to build powerful sites. We'll start by creating a simple one using the express module. Express is a great module that builds on the connect module, giving us features for creating simple or complex sites as well as for creating rest services.

Creating a Simple Page

If it's not installed already, Node.js can be installed using:

sudo yum install npm

Once installed, the first step is to create a directory for your project. Then add a file called package.json to list our dependencies. It can look like this:

 {
  "name": "TestSite",
  "description": "My Test Site",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
   "express": "3.4",
   "jade": "0.35.0"
  }
 }

Once that is created, run:

npm install This will fetch our dependencies. You could also run these separately:
npm install express
npm install jade
Now we are ready to create some simple content. First create a file called index.html. It can be simple and look like this:
<html>
<body>
  <div>
    <h3>Hello from Node.js</h3>
  </div>
</body>
</html>

Now we can create our server side javascript. We create a file called server.js which looks like this:

var express = require("express");
var app = express();
app.use(express.static(__dirname));
app.listen(8888);

Here we tell node to use the express module and we create a new express object. We then tell it to serve static content from our base directory with the __dirname keyword and then listen on port 8888. Now we can start the node server with the following:

node server.js

You will now be able to call your index.html page in the root of your server at port 8888 for example:

http://mytestsite.com:8888/index.html

Adding Asynchronous Calls with AngularJS

Now that we have a simple page hosted, we can add javascript and asynchronous calls to it. To accomplish this we will use AngularJS. AngularJS is an MVVM framework in javascript that makes it easy to bind data and events to your views, fetch data asynchronously, validate your UIs and a lot more. We can get it on our server like this:

wget https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js

To use it, we will first modify our index.html page. Using extra attributes in our HTML elements we can tell angular what controller we want to hook up to and what events and model items our controls map to:

<html ng-app>
<head>
  <script src="angular.min.js"></script>
  <script src="index.js"></script>
</head>
<body>
  <h3>Hello from Node.js</h3>
  <div ng-controller="TestController">
    <span ng-bind="txtdata"></span><br/>
    <button ng-click="getData()">Get Message</button>
  </div>
</body>
</html>

Here we took our original page and added some mark up. The ng-app label tells it this uses AngularJS while the ng-controller attribute specifies what controller to use. To give dynamic content to our span tag, we use ng-bind. Finally, we use ng-click to link a button to an event in the controller. We will use this event to fill data in to the span tag. Once the modifications are done we can create our javascript that will contain the AngularJS code. Inside index.js we put:

function TestController($scope,$http) {
  $scope.txtdata = "";
  $scope.getData = function() {
    $http.get('/getdata/').success(function(result) {
      $scope.txtdata = result.message;
    });
  }
}

This code defines our controller TestController which matches what we are looking for in our HTML. We define the data we want to bind to, txtdata, and the function getData that our button click binds to. Inside the function we call a url on our root site /getdata which we will add to node js next. The result is then stored in the txtdata variable. To allow for the /getdata call to the server side, we'll modify our server.js file. We'll simulate an object to return but it could just as easily come from a database call:

function MyCustomObject() {
  this.message = "Test messsage";
}

var myObj = new MyCustomObject();
 
app.get('/getdata', function(req, res) {
  res.send(myObj);
});

This code allows us to listen on ourpath/getdata and returns a json object with the message property. Now you can run the node server again and re-test the index.html file. Clicking the button should force a call back to the node server and return the json with the message property.

Using Jade

Jade is a templating engine that express can use to render markup as HTML and is easy to use. To duplicate the index.html file from above, we can create index.jade in our root directory and fill it like this:

doctype 5
html(lang="en" ng-app)
  body
    h3 Hello from Node.js
    #container(ng-controller="TestController")
      span(ng-bind="txtdata")
      br
      button(ng-click="getData()").
        Get Text

    script(src='angular.min.js')
    script(src='index.js')

This markup will create all the HTML tags for us and add the attributes listed in the parentheses. At the end we include our javascript files from earlier though they could also be placed in the header. To hookup the jade rendering so we can display this, we can add the following to our server.js file:

app.set('view engine', 'jade');
app.set('views', __dirname);
app.get('/index', function(req, res) {
  res.render('index.jade');
});

Now if we run this and go to our root path /index it will render a page indentical to index.html with a working angular controller.

So hopefully this shows how quick and powerful Node.js and AngularJS are. For fans of javascript, they are a nice starting point for quickly getting a site up and running. This demonstration can easily be extending by using a modules such as Mongoose or Helenus to connect to databases.