October 12th, 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 !!

  • Luis Gerardo Palacios Alvarado

    omg thanks for the tutorial and choose sails.

    could you explain me what is main.js file? its a middleware? a configuration file? its talking with angularjs? or its talking with sails?

    I awesome your tutorial for being focus

  • Luis Gerardo Palacios Alvarado

    how i add ng-routes to the example?? where the app.config would be with requirejs??

  • samchandu

    Hi tried this tutorial but while running the app at last stage it is giving an error and i found that vendor folder getting deleted .. when i type sails lift

    …can any one hlep

  • Elie GAKUBA

    I am getting same error. and every time I need to re-install bower and then go to views/layout.ejs and remove all the generated scripts to vendor and replace it with

  • Elie GAKUBA

    This is how I fixed it. move your bower.json into assets folder. then run bower install. go back to the root of your application and do sails lift.
    It should now work even when the error prompt, that will not stop the app to work. Hope that will help you.

  • S├ębastien Detranchant

    Hello, thanks for the tutorial.
    I use this app with MySQL .
    My problem is :
    When
    I start my server(sails lift) my “employee” datatable is empty but
    before the server start I have my datatable with data.
    How do I delete code for the app keep my data in my datatable?

  • Satish Dhiman

    Great tutorial and this is very helpful for startup guys and i am getting one issue bootstrap.css loading issue.
    when we lift project and trying to hit this url:
    http://localhost:1337/employee
    bootstrap.css not loading properly error is

    Request URL:http://localhost:1337/vendor/bootstrap/bootstrap.css
    Request Method:GET
    Status Code:404 Not Found