Oct 12, 2014

A Simple CRUD with MEAN Stack (MongoDB, ExpressJS, AngularJS, Node.js) + Sails.js on Windows

MEAN stack is a full JavaScript solution that helps you build fast, robust and maintainable production web applications using MongoDB, ExpressJS, AngularJS and Node.js.

Sails.js allows you to build custom, enterprise-grade Node.js apps using the Model-View-Controller pattern to organize your code so it is easier to maintain.

New MEAN Stack = MongoDB+ExpressJS+AngularJS+Node.js+Sails.js = MEANS stack

This article explains how to implement a simple CRUD by integrating AngularJS with Sails REST APIs.

Video:

Environment:

This post uses following software and modules:

Node.JS v0.10.26

MongoDB v2.4.6

Sails.JS v0.10.5

Notepad++

Windows 7

Node.JS Installation:

1. Go to Node.JS Official site, download Windows Installer and then execute it. that's it.

MongoDB Installation:

2. Go to MongoDB official site, download the zip file.

You can unzip it anywhere you like. For simplicity, I am defining path here. So in next steps, it is easy to understand for you to define relative path at your end.

Extract the downloaded zip file in E:\mongodb folder and create data and logs folder in it.

create mongodb.conf file and define db and log path in the file:


#This is an example config file for MongoDB
#basic
dbpath = E:\mongodb\data
port = 12345
logpath = E:\mongodb\logs\mongo.log

Open command prompt with "Run as administrator".

To configure a windows service for MongoDB, run following command in command prompt.


// note that following should go only in only one line
mongod -f "E:\mongodb\mongodb.conf" 
     --install --serviceName mongodb12345
     --serviceDisplayName "MongoDB Server Instance 12345"
     --serviceDescription "MongoDB Server Instance running on 12345"

To start the service, run following command:


net start mongodb12345

If you want to stop the service


net stop mongodb12345

If you want to remove the service, use following:


// note that following should go only in only one line
mongod -f "E:\mongodb\mongodb.conf" 
     --remove --serviceName mongodb12345
     --serviceDisplayName "MongoDB Server Instance 12345"
     --serviceDescription "MongoDB Server Instance running on 12345"

To connect Mongodb, run following command


mongo --port 12345

To see list of databases


show dbs

To create new database (name= sampledb)


use sampledb

To confirm that the session has the sampledb database as context

db

it returns the name of the current database

Sails.js Installation:

3. Open command prompt with "Run As Administrator" in windows, go do directory you want to create app and type following to install Sails:


cd e:\nodejs\myapp
npm install sails

To Set path of sails


set path=%path%;E:\nodejs\myapp\node_modules\.bin

Note: You can install it globally then no need to configure path.

To add bower support for Sails:


npm install sails-generate-bower

Creating Sails.js App:

4. Create new application (say myproject).


sails new myproject

You will get files created in myproject folder.

Setting Up Bower

5. Bower is a package manager for the web.

To use Bower on Windows, you must install msysgit correctly. Be sure to check the option shown below:

To setup bower


cd myproject
sails generate bower  
npm install      

To set path of bower:


set path=%path%;E:\nodejs\myapp\myproject\node_modules\grunt-bower-task\node_modules\.bin

There are some issues of installed version of bower, so we need to update it


cd node_modules\grunt-bower-task\

npm uninstall --save bower
npm install --save bower

It will update the existing bower package. Make sure you have bower 1.3.8 or newer.

now back to the project folder


cd e:\nodejs\myapp\myproject

bower cache clean

bower install --save jquery
bower install --save bootstrap
bower install --save requirejs
bower install --save angular

In bower.json, you will find the packages in dependencies:


"dependencies": {
    "jquery": "~2.1.1",
    "bootstrap": "~3.2.0",
    "requirejs": "~2.1.15",
    "angular": "~1.2.26"
  }

You will get the packages installed in assets\vendor folder.

Now lift the server:


sails lift

At this point, if you visit (http://localhost:1337/) you will see the default home page.

To check bower installed files, open following jquery file:

http://localhost:1337/vendor/jquery/jquery.js

Configure MongoDB:

6. run following command to install sails-mongo


npm install sails-mongo --save

Open config\connections.js, define mongodb database details.


module.exports.connections = {
Mongodb: {
    adapter: 'sails-mongo',
    host: 'localhost',
    port: 12345,
    user: '',
    password: '',
    database: 'sampledb'
  }
  }

Open config\models.js, set Mongodb as connection


module.exports.models = {
  connection: 'Mongodb'
}

You can also specify your connections in config\local.js to avoid commiting sensitive data to your repository.


module.exports = {
connections: {
      Mongodb: {
    adapter: 'sails-mongo',
    host: 'localhost',
    port: 12345,
    user: '',
    password: '',
    database: 'sampledb'
  }
  }
}

Generating a REST API:

7. Sails is a rapid prototyping platform , API generation is faster and easier in Sails .


sails generate api employee 

This generate an API which can search, paginate, sort, filter, create, destroy, update, and associate employees. You can check API at following location:

http://localhost:1337/employee

Sails actually generates a new Model "Employee" (api\models\Employee.js) and a Controller file EmployeeContoller ( api\controllers\EmployeeController.js).

By default, The generated model has no attribute. Let's add three attributes name, email and phone in api\models\Employee.js.


module.exports = {

  attributes: {
 
	  name:{
        type:"string",
        required:true,
        minLength: 2
      },      
      email:{
        type:"email",
        required:"true",
        unique: true
      },
	  phone:{
        type:"string",
        required:true
      }
  }
};

Now Sails provides auto-generated APIs. You can inserted values as


http://localhost:1337/employee/create?name=Brij&email=test@techbrij.com&phone=123-456-7890

and you will get below output:

You can make sure the data is properly inserted in MongoDB database as above.

You can find data by id with "/employee/id" format:

http://localhost:1337/employee/5413f1cc1364cbac10b9d6e1

8. In /api/controllers/EmployeeController.js, we can add a method to render the index page:


module.exports = {
	index: function (req, res) {
    res.view(null, {
        title: "Employee"
    });
  }
};

It will render the "Views\employee\index.ejs" (We will create it after few steps)

9. In assets/js folder create main.js file


require.config({
urlArgs: "bust=" + (new Date()).getTime(),
paths: {
'angular': '../vendor/angular/angular',
'jquery': '../vendor/jquery/jquery',
'bootstrapJs': '../vendor/bootstrap/bootstrap'
},
/**
* for libs that either do not support AMD out of the box, or
* require some fine tuning to dependency mgt'
*/
shim: {
'bootstrapJs': ['jquery'],
'angular': {'exports': 'angular'}
}
});
window.name = "NG_DEFER_BOOTSTRAP!";
require([
'angular',
'app',
'bootstrapJs'
], function(angular, app) {
'use strict';
var $html = angular.element(document.getElementsByTagName('html')[0]);
angular.element().ready(function() {
angular.resumeBootstrap([app.name]);
});
}
);

Notice at the top where we set the window.name to "NG_DEFER_BOOTSTRAP!". That will keep angular from initializing until we tell it to. Near the bottom after everything is loaded, we call angular.resumeBootstrap(). That is where angular picks back up and finishes initializing.

10. Create assets/js/app.js file to this:


define(['angular','controllers'], function (angular) {
	'use strict';  
	var app =  angular.module('employee', [
    	'controllers'
    ]);
    return app;
}); 

Angular Controller:

11. Create asset/js/controllers.js file


define(function (require) {
  
  var angular = require('angular'),
      Controllers = angular.module('controllers', []);
  
  Controllers.controller('angEmpController', require('controllers/angEmployeeController'));
  
  return Controllers;
  
});

12. Create controllers folder in assets\js folder and create angEmployeeController.js file in it.


define(function () {
    return ['$scope', '$http', function($scope, $http) {
     

function resetItem(){
   $scope.employee = {
      name : '',
      email : '',
      phone : '',
      id : ''
   };              
   $scope.displayForm = '';
  
}
resetItem();

 $scope.addItem = function () {
   resetItem();
   $scope.displayForm = true;
 }


$scope.saveItem = function () {
  var emp = $scope.employee;
      if (emp.id.length == 0){
            $http.get('/employee/create?name=' + emp.name + '&email=' +  emp.email + '&phone=' +  emp.phone).success(function(data) {
              $scope.items.push(data);
              $scope.displayForm = '';
              removeModal();
            }).
  error(function(data, status, headers, config) {
    alert(data.summary);
  });
          }
          else{
            $http.get('/employee/update/'+ emp.id +'?name=' + emp.name + '&email=' +  emp.email + '&phone=' +  emp.phone).success(function(data) {
              $scope.displayForm = '';
              removeModal();
            }).
  error(function(data, status, headers, config) {
    alert(data.summary);
  });
          }
        };

$scope.editItem = function (data) {       
        $scope.employee = data;
        $scope.displayForm = true;
}

        $scope.removeItem = function (data) {
          if (confirm('Do you really want to delete?')){
            $http['delete']('/employee/' + data.id).success(function() {
              $scope.items.splice($scope.items.indexOf(data), 1);
            });
          }
        };

        $http.get('/employee/find').success(function(data) {
          for (var i = 0; i < data.length; i++) {
            data[i].index = i;
          }
          $scope.items = data;
        });

        function removeModal(){
          $('.modal').modal('hide');          
      }

    }];
});

Views:

13. Open Views\layout.ejs file and add a line to instantiate our Angular application. We will replace the html tag with this:


<html lang="en" ng-app>

By default, CSS references:


 <link rel="stylesheet" href="/vendor/bootstrap/bootstrap.css">
 <link rel="stylesheet" href="/styles/importer.css">

Next, we need to go to bottom and keep reference of require.js only. Here is the structure:


<body>
    <%- body %>	
    <script src="/vendor/requirejs/require.js" data-main="/js/main"></script>   
  </body>

14. In the views, create folder employee and add index.ejs file


<div class="container">   
<div ng-controller="angEmpController">
 
<button class="btn btn-primary btn-lg" data-toggle="modal" data-target="#myModal" ng-click="addItem()">Add</button>
     <div class="row h3">
 		  <div class="col-md-3">Name</div>
          <div class="col-md-3">Email</div>
          <div class="col-md-3">Phone</div>
          <div class="col-md-3"></div>
     </div>

        <div class="row" ng-repeat="item in items">
          <div class="col-md-3" ng-bind="item.name"></div>
          <div class="col-md-3" ng-bind="item.email"></div>
          <div class="col-md-3" ng-bind="item.phone"></div>
          <div class="col-md-3">  
           <button class="btn btn-primary" data-toggle="modal" data-target="#myModal" ng-click="editItem(item)">Edit</button>
           <button class="btn" ng-click="removeItem(item)">Remove</button></div>
        </div>



<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" ng-show="displayForm">
  <div class="modal-dialog">
    <div class="modal-content">      
      <div class="modal-body">
      
 		  <div class="form-group">  <input type="text" ng-model="employee.name"  class="form-control" placeholder="Name"/></div>
          <div class="form-group"><input type="text" ng-model="employee.email"  class="form-control" placeholder="Email"/></div>
          <div class="form-group"> <input type="text" ng-model="employee.phone"  class="form-control" placeholder="Phone"/></div>          
     
      </div>
      <div class="modal-footer">       
     	 <button class="btn btn-primary" ng-click="saveItem()">Save</button>
     	 <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>
</div>
</div>

Output:

Source Code:

The code from this post is available on GitHub.

Conclusion:

In this post, We saw basic installation and configuration of Node.js, MongoDB and Sails.js, have a fully functional CRUD API, created the front-end with Angular.js and consumed these services.

Enjoy MEANS Stack !!