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.
Go into the newly created folder
Now initiate a project by npm init command as
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:
taskCtrl.js: File containing the business logic or some extra operation to be done internally, calls the data layer service
taskService.js: The main data access layer where ground level DB operations are manipulated.
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:
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:
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.
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:
main.js: The main Angular js module file where application is bootstrapped and routes are defined.
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.
javascripts/listCtrl.js
tpls/list.html
javascripts/createCtrl.js
Finally, all the files needs to be referred in index.html. Hence index.html becomes:
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! :)
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:
- If you don't have MongoDB, download it from here and install.
- Make sure you have node and npm installed, if not set it up from here.
- 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:
- body-parser : To extract data from request body to consume POST data.
- express : The underlying back-end server which will serve as REST.
- 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]
At this point in time, we should have all the dependencies in node_modules folder and also added in package.json file.
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.
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.
db.js mongo configuration file (code information provided inline):
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 :
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:
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:
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
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:
- In the root directory, create a new file say index.js (entry point of application)
- Create a new folder called public (container for all static element html/js/css/img)
- 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 8082So, 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:
- angular.min.js (The core angular file)
- angular-route.min.js (The routing for angular)
- angular-sanitize.min.js (angular safe parsing html)
- angularjs-datetime-picker.min.js (A minimal date time picker angular component)
- ngToast.min.js (ngToast for displaying notifications)
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}}">✎</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! :)
Awesome description
ReplyDeleteThanks, glad that you liked it!
DeleteNice one really help us
ReplyDeleteHappy to hear that :)
DeleteGreat work!! :)
ReplyDeleteThanks for reading
Delete