Skip to main content

Node, Angular1.x,MongoDB, Express Task List - POC

Welcome folks!
This is a series of 2 tutorials. The first part is about Node, Angular 1.x, MongoDB, Express and second part is to re-create application using Angular4. We will settle with 4.x rather than 2.x so that we get familiarized with few 4.x concepts. Intended both for new users trying hands on with Angular and Angular 1.x experts as well.

The following is the first part of tutorial where we will create a simple backend server in Express exposed via a pure REST model serving client, created in Angular 1.x which uses database as MongoDB. If you are well versed with Node and Angular 1.x ignore this and move on to second half of the tutorial, i.e., developing application in Angular4.

Pre-requisite:

  1. If you don't have MongoDB, download it from here and install.
  2. Make sure you have node and npm installed, if not set it up from here.
  3. Install bower globally for package manager - from command prompt hit  npm install -g bower. 

What are we going to design?

Here, we'll design a task CRUD operation. The application will allow a user to see a list of tasks, edit or delete them and also create new task. The whole source code is available on Github. To see the code in action see the DEMO.

Getting Started:

Let us call our application as task-crud. Go to a location in command prompt where you want the application to be created and create a new folder task-crud

D:\projects> mkdir task-crud

Go into the newly created folder

D:\projects> cd task-crud

Now initiate a project by npm init command as

D:\projects\task-crud> npm init

It will ask for few details like appName, version, description, author, etc. You can by-pass all by hitting enter or filling relevant details, for simplicity let the entry point be index.js. At the end, you will get a package.json file inside root directory, from here on we will refer \task-crud as root directory.

Now its time to install dependencies to our project, The dependencies are:
  1. body-parser : To extract data from request body to consume POST data.
  2. express : The underlying back-end server which will serve as REST.
  3. mongodb : The connector for communicating with mongo database.
To install these dependencies we will use npm install <component-name> --save. [--save will ensure it gets added to package.json dependency list]

D:\projects\task-crud> npm install body-parser --save

D:\projects\task-crud> npm install express --save

D:\projects\task-crud> npm install mongodb --save

At this point in time, we should have all the dependencies in node_modules folder and also added in package.json file.

Application REST and Client

Let's create a simple http server which will serve a single static html page for now and later we'll modify it down the line. We'll be using static path concept of node to serve a static directory let's say public directory. Hence: 
  1. In the root directory, create a new file say index.js (entry point of application)
  2. Create a new folder called public  (container for all static element html/js/css/img)
  3. Create a new html file say index.html inside public folder (the file to be served).

index.js

The entry point of the application which will host server and serve static files. Time to add content to index.js paste the following code, details of each statement are defined in inline comments:

 //importing the express server from dependency  
 var express = require('express');  
   
 //importing body-parser so to parse body params in POST request  
 var bodyParser = require('body-parser');  
   
 //path module to serve static content   
 var path = require('path');  
   
 //defining app  
 var app = express();  
   
 //defining port  
 var port = 8082;  
   
 // configure the app to use bodyParser()  
 app.use(bodyParser.urlencoded({  
   extended: true  
 }));  
 app.use(bodyParser.json());  
   
 //provide details of static resources to be served  
 app.use(express.static(path.join(__dirname, 'public')));  
   
 //make the server listen for request  
 app.listen(port,"0.0.0.0",function(){  
    console.log("App successfully started at port " + port);  
 });  

index.html

Currently, its a simple file containing a h1 block. We'll later add client code here:

 <!DOCTYPE html>  
 <html lang="en">  
 <head>  
   <meta charset="UTF-8">  
   <title>Task-crud</title>  
 </head>  
 <body>  
 <h1>Welcome, Our page is served</h1>  
 </body>  
 </html>  

At this moment if you try to run the application as node index.js and hit enter you will get a message "App successfully started at port 8082" and pointing your browser to localhost:8082 will display the h1 welcome message.

D:\projects\task-crud> node index.js
App successfully started at port 8082


So, now we've created our server and also seen it in action. Let's add MongoDB dependency and REST API for getting all tasks from database. To isolate, we will do all client side coding in public folder and all server side coding in server folder. Server folder will be created inside root directory.

MongoDB configuration

Create a file called db.js inside server folder which will contain the database information and that will be later used by index.js file by module exports method.

db.js mongo configuration file (code information provided inline):

 //require mongoclient from mongodb dependency  
 var MongoClient = require('mongodb').MongoClient;  
   
 //our local mongodb url where tasks is the name of the database  
 var url = 'mongodb://localhost:27017/tasks';  
   
 //the whole module will be exported to index.js  
 module.exports = {  
    initialize: function(next){  
     //A middleware hook to perform extra operations currently will initialize DB  
     this.initializeDB(next);  
    },  
    initializeDB: function(next){  
     //connect to mongoDB if error throw error else return to callback  
     MongoClient.connect(url, function(err, db) {  
      if(err){  
       next(err);  
      }else{  
       module.exports.db = db;  
       next();  
      }  
     });  
    }  
 }  

Now, its time to use this file in index.js file and start our server if connected to database, modify the file index.js as :

 //importing the express server from dependency  
 var express = require('express');  
   
 //importing body-parser so to parse body params in POST request  
 var bodyParser = require('body-parser');  
   
 //path module to serve static content  
 var path = require('path');  
   
 //importing the Database config file  
 var dbs = require("./server/db");  
   
 //defining app  
 var app = express();  
   
 //defining port  
 var port = 8082;  
   
 // configure the app to use bodyParser()  
 app.use(bodyParser.urlencoded({  
   extended: true  
 }));  
 app.use(bodyParser.json());  
   
 //provide details of static resources to be served  
 app.use(express.static(path.join(__dirname, 'public')));  
   
 //calling the Db initialize function and passing callback so that it can be ensured server starts only if connected to DB  
 dbs.initialize(function(err) {  
  if(err){  
   console.log("Error : mongo not running");  
  }else{  
   app.listen(port,"0.0.0.0",function(){  
     console.log("App successfully started at port " + port);  
   });  
  }  
 });  

Changes include adding DB and starting server if connected to DB. If you now try to run node index.js without starting mongoDB you will get error "Error : mongo not running". 

Open a different terminal and run:

C:\> mongod

If you've done mongoDB setup properly and have data/db folder up, you will get mongo db running. Now, you can run node index.js in terminal. You will get connected to server showing message "App successfully started at port 8082 

To recall, we've set up node server with live mongoDB connected to tasks database (don't worry regarding the tasks DB it will be created on the fly). Time to add server routing for REST calls using a modularized approach. Let's create 4 more files under server folder hence the structure of server folder will now look like:

server
|--- db.js (already created for DB configuration)
|--- taskRoute.js (all the API routes will be defined here)
|--- taskCtrl.js (controller for all routes which will redirect to appropriate service - business layer)
|--- taskService.js (the database interaction code)
|--- response.js (a global object for API response success/error)

taskRoute.js: A pure REST API definition routed to specific controller business logic function

 //get the router object from express app  
 var express = require('express');  
 var router = express.Router();  
   
 //the controller to be called to each route  
 var taskCtrl = require('./taskCtrl');  
   
 //when url is task/ GET <get list of tasks>  
 router.get('/', taskCtrl.getAllTask);  
   
 //when url is task/:id GET <get single task of id>  
 router.get('/:id', taskCtrl.getSingleTask);  
   
 //when url is task/:id PUT <update single task of id>  
 router.put('/:id', taskCtrl.editTask);  
   
 //when url is task/ POST <create single task from posted data>  
 router.post('/', taskCtrl.insertTask);  
   
 //when url is task/:id DELETE <delete single task of id>  
 router.delete('/:id', taskCtrl.deleteTask);  
   
 //export this so to be used inside index.js  
 module.exports = router;  

taskCtrl.js: File containing the business logic or some extra operation to be done internally, calls the data layer service

 //import service data layer/ response to be consumed  
 var taskService = require('./taskService');  
 var errorResponse = require('./response').error;  
 var successResponse = require('./response').success;  
   
 module.exports = {  
   //create a new task and stores into DB  
   insertTask:function(req,res){  
     taskService.insertTask(req.body,function(err,dt){  
      if(err){  
        //if error send error response  
        return res.send(errorResponse(err));  
      }  
      //else send success response  
      res.send(successResponse(dt,null));  
     });  
   },  
   
   //updates a task in DB  
   editTask:function(req,res){  
     taskService.editTask(req.params.id,req.body,function(err,dt){  
      if(err){  
        return res.send(errorResponse(err));  
      }  
      res.send(successResponse(dt,null))  
     });  
   },  
   
   //deletes a task from DB  
   deleteTask:function(req,res){  
     taskService.deleteTask(req.params.id,function(err,dt){  
      if(err){  
        return res.send(errorResponse(err));  
      }  
      res.send(successResponse(dt,null))  
     });  
   },  
   
   //fetch single task with given id  
   getSingleTask:function(req,res){  
     //console.log(req.params.id);  
     taskService.getSingleTask(req.params.id,function(err,dt){  
      if(err){  
        return res.send(errorResponse(err));  
      }  
      res.send(successResponse(dt,null))  
     });  
   },  
   
   //fetch all avilable tasks  
   getAllTask:function(req,res){  
     taskService.getAllTask(null,function(err,dt){  
      if(err){  
        return res.send(errorResponse(err));  
      }  
      res.send(successResponse(dt,null))  
     });  
   }  
 }  

taskService.js: The main data access layer where ground level DB operations are manipulated.

 //import the DB configuration and mongodb driver  
 var database = require('./db');  
 var mongodb = require('mongodb');  
   
 module.exports = {  
   insertTask:function(data,cb){  
     //database action for create task  
     database.db.collection("task").insertOne(data,function(err,dt){  
       if(err){  
         return cb(err,null);  
       }  
       return cb(null,data);  
     });  
   },  
   editTask:function(id,data,cb){  
     //database action for updating task by an given _id   
     var where = {"_id":new mongodb.ObjectID(id)}  
     database.db.collection("task").updateOne(where,{$set:data},function(err,dt){  
       if(err){  
         return cb(err,null);  
       }  
       return cb(null,dt);  
     });  
   },  
   deleteTask:function(id,cb){  
     //database action for deleting task by an given _id   
     database.db.collection("task").deleteOne({"_id": new mongodb.ObjectID(id)},function(err,dt){  
       if(err){  
         return cb(err,null);  
       }  
       return cb(null,dt);  
     });  
   },  
   getSingleTask:function(id,cb){  
     //database action to fetch single task by an given _id  
     database.db.collection("task").findOne({"_id":new mongodb.ObjectID(id)},function(err,dt){  
       if(err){  
         return cb(err,null);  
       }  
       return cb(null,dt);  
     });  
   },  
   getAllTask:function(data,cb){  
     //database action to fetch all tasks   
     database.db.collection("task").find().toArray(function(err,dt){  
       if(err){  
         return cb(err,null);  
       }  
       return cb(null,dt);  
     });  
   }  
 }  

The Application will route as:

client > taskRoute > taskCtrl > taskService > taskCtrl > taskRoute > Client

Client will hit REST API which is filtered by taskRoute, so it knows which controller function to call. In controller, the relevant data access layer stub is resolved. After performing operation, the path returns back to client in similar fashion with relevant data.

response.js: Instead of hard coding request response object in each data access layer, we will create a global object which can be used anywhere:

 module.exports = {  
   //error response  
   error:function(err){  
     return{  
       status:"ERROR",  
       message:err  
     };  
   },  
   //success response  
   success:function(dt,msg){  
     return{  
       status:"SUCCESS",  
       data:dt,  
       message:msg  
     };  
   }  
 }  

At this point we're almost done with server part. We just need to use the route file taskRoute in index.js so that our http REST gets activated. Hence index.js now becomes:

 //importing the express server from dependency  
 var express = require('express');  
   
 //importing body-parser so to parse body params in POST request  
 var bodyParser = require('body-parser');  
   
 //path module to serve static content  
 var path = require('path');  
   
 //import route for REST  
 var task = require('./server/taskRoute');  
   
 //importing the Database config file  
 var dbs = require("./server/db");  
   
 //defining app  
 var app = express();  
   
 //defining port  
 var port = 8082;  
   
 // configure the app to use bodyParser()  
 app.use(bodyParser.urlencoded({  
   extended: true  
 }));  
 app.use(bodyParser.json());  
   
 //provide details of static resources to be served  
 app.use(express.static(path.join(__dirname, 'public')));  
   
 //let the app use task  
 app.use('/task', task);  
   
 //calling the Db initialize function and passing callback so that it can be ensured server starts only if connected to DB  
 dbs.initialize(function(err) {  
  if(err){  
   console.log("Error : mongo not running");  
  }else{  
   app.listen(port,"0.0.0.0",function(){  
     console.log("App successfully started at port " + port);  
   });  
  }  
 });  

Yay! We're done with the server part. Try to run node index.js and then use POSTMAN or browser with URL http://localhost:8082/task. You will see the response from the server. Being done with the server, let's create a minimal angular app in 1.x to consume these APIs and display the data accordingly.

Client Side - Angular 1.x

Let's first add some more static paths in index.js for serving js/css/images as we did for index.html as:

 app.use("/styles", express.static(__dirname + '/public/stylesheets'));  
 app.use("/scripts", express.static(__dirname + '/public/javascripts'));  
 app.use("/img", express.static(__dirname + '/public/images'));  
 app.use("/templates", express.static(__dirname + '/public/tpls'));  

Now our public folder will look like:

public
|--- javascripts (All the js files)
|--- stylesheets (All the css files)
|--- tpls (All the template files used for angular routes)

The vendor files used for the angular webApp are:
  1. angular.min.js  (The core angular file)
  2. angular-route.min.js  (The routing for angular)
  3. angular-sanitize.min.js  (angular safe parsing html)
  4. angularjs-datetime-picker.min.js  (A minimal date time picker angular component)
  5. ngToast.min.js  (ngToast for displaying notifications)
Download all these files and place inside /javascripts. Now, its time to write our custom code. Let's continue and bootstrap the angular module.

main.js: The main Angular js module file where application is bootstrapped and routes are defined.

 //bootstraping the angular application  
 var app = angular.module("task",["ngRoute","angularjs-datetime-picker","ngToast"]);  
   
 //route config  
 app.config(function($routeProvider,$locationProvider) {  
   $locationProvider.hashPrefix('');  
   $routeProvider  
   .when("/", {  
     templateUrl : "templates/list.html",  
     controller : "listCtrl",  
     controllerAs : "vm"  
   })  
   .when("/tasks", {  
     templateUrl : "templates/task.html",  
     controller : "createCtrl",  
     controllerAs : "vm"  
   })  
   .when("/tasks/:id", {  
     templateUrl : "templates/task.html",  
     controller : "editCtrl",  
     controllerAs : "vm"  
   })  
   .otherwise({redirectTo:'/'});  
 });  

templateUrl resolves from the static path which is defined in index.js as /public/tpls., tpls is a new folder which needs to be created under public folder as a container for route templates. Please note that we're using "vm" notation to resolve controllerAs syntax so that $scope binding can be avoided.
Note : We're using same template in create and edit task just the controller differs.

taskService.js: Don't confuse this with one under \server. This is a different file under \public which handles the server call in angular. As a best practice, it is advised not to call server from controller, instead we'll create a angular factory/service.

 //defining an angular service  
 angular.module("task").factory('taskService',function($http){  
   var taskService = {};  
   var url = "/task"  
     
   //create task  
   taskService.createTask = function(data){  
     return $http.post(url,data);  
   };  
   
   //edit task  
   taskService.editTask = function(id,data){  
     return $http.put(url+"/"+id,data);  
   };  
   
   //get all tasks  
   taskService.getTasks = function(){  
     return $http.get(url);  
   };  
   
   //get single task by ID  
   taskService.getTask = function(id){  
     return $http.get(url+"/"+id);  
   };  
   
   //delete single task by ID  
   taskService.deleteTask = function(id){  
     return $http.delete(url+"/"+id);  
   };  
     
   return taskService;  
 });  

List View

It is the default location of route which loads all task from server and displays onto the table. Actionable links are edit/delete for each task. Let's see the ctrl and template file:

javascripts/listCtrl.js
 angular.module("task").controller('listCtrl',['taskService','ngToast',function(taskService,ngToast){  
   
   var vm = this;  
   vm.tasks = []  
     
   //load task from server  
   vm.loadTasks = function(){  
     taskService.getTasks().then(function(success){  
     vm.tasks = success.data.data;  
   },function(err){  
     alert(err);  
   });    
   }  
     
   //delete task on request  
   vm.deleteTask = function(id){  
     if(!confirm("Are you sure you want to delete this record ?"))  
       return;  
     taskService.deleteTask(id).then(function(success){  
       ngToast.create("Task Deleted successfully");  
       vm.loadTasks();  
     },function(err){  
       ngToast.create("Failed, try again!");  
     })  
   }  
   vm.loadTasks();  
 }])  

tpls/list.html
 <div>  
 <div ng-if="vm.tasks.length > 0">  
   <h2 class="text-center"> Task List ( <a href="#/tasks/"> Add New </a>)</h2>  
   <table class="table table-bordered table-hover" >  
   <thead>  
   <tr><th>No.</th><th>Title</th><th>Description</th><th>Time</th><th>Edit</th><th>Delete</th></tr>  
   </thead>  
   <tbody>  
   <tr ng-repeat="eachTask in vm.tasks">  
     <td>  
       {{$index+1}}  
     </td>  
     <td>  
       {{eachTask.title}}  
     </td>  
     <td>  
       {{eachTask.desc}}  
     </td>  
     <td>  
       {{eachTask.taskdt}}  
     </td>  
     <td>  
     <a style="color:#000000" ng-href="#/tasks/{{eachTask._id}}">&#9998;</a>  
     </td>  
     <td>  
       <span style="cursor:pointer;" ng-click="vm.deleteTask(eachTask._id)">X</span>  
     </td>  
   </tr>  
   </tbody>  
 </table>  
 </div>  
   
   
 <div ng-if="vm.tasks.length == 0" class="text-center">No Data Available Click <a href="#/tasks/"> here </a> to insert task</div>  
 </div>  

Create Task View

This is a view for creating a new Task. Its also used for edit purpose. In case of create, the template renders with blank data, whereas in case of edit, it renders with selected task data.

tpls/task.html
 <h2 class="text-center"> Task Details</h2>  
 <form class="form-horizontal" name="taskForm">  
   <div class="form-group">  
     <label class="control-label col-sm-2" for="tlt">*Title:</label>  
     <div class="col-sm-8">  
       <input type="text" ng-model="vm.task.title" class="form-control" id="tlt" placeholder="Enter Task Title" required>  
     </div>  
   </div>  
   <div class="form-group">  
     <label class="control-label col-sm-2" for="desct">Description:</label>  
     <div class="col-sm-8">  
       <textarea rows="5" ng-model="vm.task.desc" class="form-control" id="desct" placeholder="Enter Descption"></textarea>  
     </div>  
   </div>  
   <div class="form-group">  
     <label class="control-label col-sm-2" for="taskdt">*Task DateTime:</label>  
     <div class="col-sm-8">  
       <input datetime-picker date-format="yyyy-MM-dd HH:mm:ss" future-only ng-model="vm.task.taskdt" class="form-control" id="taskdt" placeholder="select datetime" required/>  
     </div>  
   </div>  
   <div class="form-group">  
     <div class="col-sm-offset-2 col-sm-8">  
       <button ng-click="vm.createTask(false)" ng-disabled="!taskForm.$valid" type="submit" class="btn btn-success">Save</button>  
       <button ng-click="vm.createTask(true)" ng-disabled="!taskForm.$valid" type="submit" class="btn btn-primary">Save and Add New</button>  
     </div>  
   </div>  
 </form>  

javascripts/createCtrl.js
 angular.module("task").controller('createCtrl',['taskService','$location','ngToast',function(taskService,$location,ngToast){  
     var vm = this;  
     //render blank  
     vm.task = {  
       title:"",  
       desc:"",  
       taskdt:""  
     }  
       
     //on save create task  
     vm.createTask = function(isAdd){  
       taskService.createTask(this.task).then(function(success){  
         ngToast.create("Task Created successfully");  
         if(isAdd){  
           vm.task = {  
             title:"",  
             desc:"",  
             taskdt:""  
           }  
         }else{  
           $location.path("/");  
         }  
       },function(err){  
         ngToast.create("Failed Try again !");  
       });  
     }  
 }])  

Edit Task View

Template is same as used in create just the controller differs

javascripts/editCtrl.js
 angular.module("task").controller('editCtrl',['taskService','$routeParams','ngToast','$location',function(taskService,$routeParams,ngToast,$location){  
   
     var vm = this;  
     vm.taskId = $routeParams.id;  
   
   
     taskService.getTask(this.taskId).then(function(success){  
       vm.task = {  
             title:success.data.data.title,  
             desc:success.data.data.desc,  
             taskdt:success.data.data.taskdt  
           }  
     },function(err){  
       $location.path("/tasks/");  
     });  
   
   
     vm.createTask = function(isAdd){  
       delete vm.task._id;  
       taskService.editTask(vm.taskId,vm.task).then(function(success){  
         ngToast.create("Task Updated successfully");  
         if(isAdd){  
           $location.path("/tasks/");  
         }else{  
           $location.path("/");  
         }  
       },function(err){  
         ngToast.create("Failed, try again!");  
       });  
     }  
   
 }])  

Finally, all the files needs to be referred in index.html. Hence index.html becomes:
 <html ng-app="task">  
  <head>  
   <title>  
    Tasks POC  
   </title>  
   <link rel="stylesheet" href="/styles/main.css">  
   <link rel="stylesheet" href="/styles/angularjs-datetime-picker.css">  
   <link rel="stylesheet" href="/styles/ngToast.min.css">  
   <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">  
  </head>  
  <body>  
    <toast></toast>  
   <div class="container">  
    <ng-view></ng-view>  
   </div>  
   
  </body>  
  <script src="/scripts/angular.min.js"></script>  
  <script src="/scripts/angular-sanitize.min.js"></script>  
  <script src="/scripts/angular-route.min.js"></script>  
  <script src="/scripts/angularjs-datetime-picker.min.js"></script>  
  <script src="/scripts/ngToast.min.js"></script>  
  <script src="/scripts/main.js" type="text/javascript"></script>  
  <script src="/scripts/taskService.js"></script>  
  <script src="/scripts/listCtrl.js" type="text/javascript"></script>  
  <script src="/scripts/createCtrl.js" type="text/javascript"></script>  
  <script src="/scripts/editCtrl.js" type="text/javascript"></script>  
   
 </html>  

Congratulations! You're done with server and client side task application. Hit "node index.js" and you will get the application serving at localhost:8082 with task list as default route.

Result: For working Demo visit here and for source code at github


Upgrade Angular 1.x to Angular4.x: PART2.

I hope you've found this blog useful. Please comment your suggestions or feedback. In case you've any queries, feel free to drop me a mail.

Happy Coding! :)

Comments

Post a Comment

Popular posts from this blog

Developing application in Angular4

Welcome again folks! This is in continuation of a previous blog where we created Task List crud - POC in Node, Express, mongo and Angular 1.x. We will follow the same back-end and will try to build a new front-end using Angular4. Pre-requisite : People who have gone through PART1  know the dependency which was mentioned, rest who are starting from here please go through the pre-requisite of PART1 session.  You need to have angular-cli installed. We will use it to scaffold our application and for development and production builds as well.  What are we going to build? We'll be building the same Task Crud application as we did in previous session, but with upgraded version of Angular and Material design. The source code for the application can be found at github  and to see the app in action visit the DEMO . Getting started: To get started we will require a back-end enabled with REST APIs. Let's use the same old Task Server from previous session. You c...

Adding custom charts inside d3 circle pack visualization.

Hello folks, I'm back with a new article. Recently one of my close friend was working on d3 circle pack visualization whereby he wanted to show charts at the leaf node, which he pretty much accomplished but was struggling to make it work to resize of zoom-in and zoom-out circle. That was the point when I got involved in this use case and it gave me an opportunity to share here how we got the break through. What are we going to build ? A circle pack visualization whose leaf node contains a custom chart like this: To see a live demo visit here . Context: The above visualization is drawn under the context of e-commerce store where nested elements are products under category and sub category. The leaf node is a product whose children are sales data of the week. Lets get started:  The data for the chart is as follows: { "name": "products", "children": [ { "name": "electronics", "children...