Monday, June 2, 2014

REST API development with NodeJS and Mongo DB

Brief description about NodeJS:
Node.js is a platform built on Chrome's JavaScript runtime for easily building fast,
scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.


So lets start with our main topic, without discussing more about other things.
Developing Rest APIs with node is also having one or more node module available in the market like expressJS and restify.js. Lots of pros and cons are also available on net.

For me everything has its own pros & cons, so i am using Restify.js here. As i find it easy.
So install NodeJS & MongoDb in your operating system and register it globally then install the required module.
1: MongoDB.
2: RestifyJS.
3: MongoJS.

MongoDB can be downloaded from http://www.mongodb.org/ and after installing nodeJS which can downloaded from http://nodejs.org/, you can instal restify and mongojs easily.

Commands to install both one by one.
1: npm install restify
2:npm install mongojs

Now create a directory and go inside that directory, within that directory create a package.json file.
This package.json file will be used when you package your project for distribution.
To auto generate package.json type in terminal "npm init" (Windows user first register node and npm in environment variable to access it globally.)

Command and its uses:
"npm init"

My Demo App Details: $ npm init This utility will walk you through creating a package.json file. It only covers the most common items, and tries to guess sane defaults. See `npm help json` for definitive documentation on these fields and exactly what they do. Use `npm install --save` afterwards to install a package and save it as a dependency in the package.json file. Press ^C at any time to quit. name: (app) HelloRestAPI version: (0.0.0) 1.0.0 description: This is my first rest API project entry point: (index.js) app.js test command: node app.js git repository: https://github.com/ashishnigam/Team_Dev_Work/tree/master/Servers/nodeJS/myapp keywords: Rest,REST, REST APIs, API, APIs, Rest API, Node API, NPM. author: Ashish Nigam license: (BSD-2-Clause) Copyright (c) 2014, Ashish Nigam All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. About to write to /Users/ashish.nigam/Documents/NodeHTTP_Server/app/package.json: {   "name": "HelloRestAPI",   "version": "1.0.0",   "description": "This is my first rest API project",   "main": "app.js",   "scripts": {     "test": "node app.js"   },   "repository": {     "type": "git",     "url": "https://github.com/ashishnigam/Team_Dev_Work/tree/master/Servers/nodeJS/myapp"   },   "keywords": [     "Rest",     "REST",     "REST",     "APIs",     "API",     "APIs",     "Rest",     "API",     "Node",     "API",     "NPM."   ],   "author": "Ashish Nigam",   "license": "Copyright (c) 2014, Ashish Nigam All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." } Is this ok? (yes) yes

After this, your package.json is generated and you can check the content of package.json by opening in any text editor.

After that to install the dependencies and add it to package.json, execute the below mentioned command in the terminal (within same directory in terminal).


$ npm install restify --save
$ npm install mongojs --save

Similarly you can install any dependency and add it package.json. a dependency key will be added automatically once you use the above mentioned command.

My package.json file generated:

{
  "name": "HelloRestAPI",
  "version": "1.0.0",
  "description": "This is my first rest API project",
  "main": "app.js",
  "scripts": {
    "test": "node app.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/ashishnigam/Team_Dev_Work/tree/master/Servers/nodeJS/myapp"
  },
  "keywords": [
    "Rest",
    "REST",
    "REST",
    "APIs",
    "API",
    "APIs",
    "Rest",
    "API",
    "Node",
    "API",
    "NPM."
  ],
  "author": "Ashish Nigam",
  "license": "Copyright (c) 2014, Ashish Nigam All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.",
  "dependencies": {
    "mongojs": "~0.13.0",
    "restify": "~2.8.1"
  }
}
See the dependency section, which i made it bold to highlight.

Now create a app.js file in the same directory and open it in your favourite text editor.

Require both the dependency in your app.js at the top. copy paste to write it at the top of your app.js

var restify = require('restify'); 
var mongojs = require("mongojs"); 
The two lines show above load the restify and mongojs modules using the require function and assign them to variables.
Now create a new server using restify API:
var ip_addr = '127.0.0.1';
var port    =  '8080';
 
var server = restify.createServer({
    name : "myapp"
});
 
server.listen(port ,ip_addr, function(){
    console.log('%s listening at %s ', server.name , server.url);
});
The code shown above creates a new server. The createServer() function takes an options object. We passed myapp as the name of the server in options object. You can view the full list of options in the documentation. After create the server instance, we call the listen function passing port, ip address, and a callback function.

Run the application by typing the following command.

$ node app.js
You will see following on the command line terminal.
myapp listening at http://127.0.0.1:8080

Configure Plugins

The restify module has a lot of built in plugins which we can use. Copy and paste the following in app.js. These should be added before the server.listen() function. Refer to documentation to learn about all the supported plugins.
server.use(restify.queryParser());
server.use(restify.bodyParser());
server.use(restify.CORS());

The three lines shown above :
  1. The restify.queryParser() plugin is used to parse the HTTP query string (i.e., /jobs?skills=java,mysql). The parsed content will always be available in req.query.
  2. The restify.bodyParser() takes care of turning your request data into a JavaScript object on the server automatically.
  3. The restify.CORS() configures CORS support in the application.

Configure MongoDB

Before adding the routes, let's add code to connect to myapp to the MongoDB database.
var connection_string = '127.0.0.1:27017/myapp';
var db = mongojs(connection_string, ['myapp']);
var jobs = db.collection("jobs");
In the code shown above, we connect to local MongoDB instance. Next, we get the jobs collection using database object.

Writing CRUD API

Now, we have the server and database part ready. We still need routes to define the behaviour of the API. Copy and paste the following code to app.js.
var PATH = '/jobs'
server.get({path : PATH , version : '0.0.1'} , findAllJobs);
server.get({path : PATH +'/:jobId' , version : '0.0.1'} , findJob);
server.post({path : PATH , version: '0.0.1'} ,postNewJob);
server.del({path : PATH +'/:jobId' , version: '0.0.1'} ,deleteJob);
The code shown above does the following:
  1. When a user makes a GET request to '/jobs', then findAllJobs callback will be invoked. The another interesting part is the use of versioned routes. A client can specify the version using Accept-Version header.
  2. When a user makes a GET request to '/jobs/123', then findJob callback will be invoked.
  3. When a user makes POST request to '/jobs', then postNewJob callback will be invoked.
  4. When a user makes DELETE request to '/jobs/123', then postNewJob callback will be invoked.
Now we will write the callbacks. Copy and paste the following to app.js.
function findAllJobs(req, res , next){
    res.setHeader('Access-Control-Allow-Origin','*');
    jobs.find().limit(20).sort({postedOn : -1} , function(err , success){
        console.log('Response success '+success);
        console.log('Response error '+err);
        if(success){
            res.send(200 , success);
            return next();
        }else{
            return next(err);
        }
 
    });
 
}
 
function findJob(req, res , next){
    res.setHeader('Access-Control-Allow-Origin','*');
    jobs.findOne({_id:mongojs.ObjectId(req.params.jobId)} , function(err , success){
        console.log('Response success '+success);
        console.log('Response error '+err);
        if(success){
            res.send(200 , success);
            return next();
        }
        return next(err);
    })
}
 
function postNewJob(req , res , next){
    var job = {};
    job.title = req.params.title;
    job.description = req.params.description;
    job.location = req.params.location;
    job.postedOn = new Date();
 
    res.setHeader('Access-Control-Allow-Origin','*');
 
    jobs.save(job , function(err , success){
        console.log('Response success '+success);
        console.log('Response error '+err);
        if(success){
            res.send(201 , job);
            return next();
        }else{
            return next(err);
        }
    });
}
 
function deleteJob(req , res , next){
    res.setHeader('Access-Control-Allow-Origin','*');
    jobs.remove({_id:mongojs.ObjectId(req.params.jobId)} , function(err , success){
        console.log('Response success '+success);
        console.log('Response error '+err);
        if(success){
            res.send(204);
            return next();      
        } else{
            return next(err);
        }
    })
 
}
The code shown above is self explanatory. We are using mongojs API to perform CRUD operations.
We can test the web services using curl. To create a new job, type the command shown below.
$ curl -i -X POST -H "Content-Type: application/json" -d '{"title":"NodeJS Developer Required" , "description":"NodeJS Developer Required" , "location":"Sector 30, Gurgaon, India"}' http://127.0.0.1:8080/jobs
To find all the jobs
$ curl -is http://127.0.0.1:8080/jobs
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json
Content-Length: 187
Date: Sun, 24 Nov 2013 16:17:27 GMT
Connection: keep-alive
 
[{"title":"NodeJS Developer Required","description":"NodeJS Developer Required","location":"Sector 30, Gurgaon, India","postedOn":"2013-11-24T16:16:16.688Z","_id":"52922650aab6107320000001"}]
Complete app.js 
// Below shared app.js is prepared for uploading same project in open shift (RedHat directory).
// Will be sharing details of how to upload on open shift in my next blog.
#!/bin/env node
//  OpenShift sample Node application
var restify = require('restify');
var mongojs = require("mongojs");

var ip_addr = process.env.OPENSHIFT_NODEJS_IP   || '127.0.0.1';
var port    = process.env.OPENSHIFT_NODEJS_PORT || '8080';

var db_name = process.env.OPENSHIFT_APP_NAME || "localjobs";

var connection_string = '127.0.0.1:27017/' + db_name;
// if OPENSHIFT env variables are present, use the available connection info:
if(process.env.OPENSHIFT_MONGODB_DB_PASSWORD){
  connection_string = process.env.OPENSHIFT_MONGODB_DB_USERNAME + ":" +
  process.env.OPENSHIFT_MONGODB_DB_PASSWORD + "@" +
  process.env.OPENSHIFT_MONGODB_DB_HOST + ':' +
  process.env.OPENSHIFT_MONGODB_DB_PORT + '/' +
  process.env.OPENSHIFT_APP_NAME;
}

var db = mongojs(connection_string, [db_name]);
var jobs = db.collection("jobs");

var items = db.collection("items");

var server = restify.createServer({
    name : "localjobs"
});

server.pre(restify.pre.userAgentConnection());
server.use(restify.acceptParser(server.acceptable));
server.use(restify.queryParser());
server.use(restify.bodyParser());
server.use(restify.CORS());

function findAllJobs(req, res , next){
    res.setHeader('Access-Control-Allow-Origin','*');
    jobs.find().limit(20).sort({postedOn : -1} , function(err , success){
        console.log('Response success '+success);
        console.log('Response error '+err);
        if(success){
            res.send(200 , success);
            return next();
        }else{
            return next(err);
        }
        
    });
    
}

function findJob(req, res , next){
    res.setHeader('Access-Control-Allow-Origin','*');
    jobs.findOne({_id:mongojs.ObjectId(req.params.jobId)} , function(err , success){
        console.log('Response success '+success);
        console.log('Response error '+err);
        if(success){
            res.send(200 , success);
            return next();
        }
        return next(err);
    })
}

function findItem(req, res , next){
    res.setHeader('Access-Control-Allow-Origin','*');
    items.findOne({_id:mongojs.ObjectId(req.params.itemId)} , function(err , success){
        console.log('Response success '+success);
        console.log('Response error '+err);
        if(success){
            res.send(200 , success);
            return next();
        }
        return next(err);
    })
}

function postNewItem(req , res , next){
    var item = {};
    item.title = req.params.title;
    item.description = req.params.description;
    item.value = req.params.value;
    item.postedOn = new Date();

    res.setHeader('Access-Control-Allow-Origin','*');
    
    items.save(item , function(err , success){
        console.log('Response success '+success);
        console.log('Response error '+err);
        if(success){
            res.send(201 , item);
            return next();
        }else{
            return next(err);
        }
    });
}

function postNewJob(req , res , next){
    var job = {};
    job.title = req.params.title;
    job.description = req.params.description;
    job.location = req.params.location;
    job.postedOn = new Date();

    res.setHeader('Access-Control-Allow-Origin','*');
    
    jobs.save(job , function(err , success){
        console.log('Response success '+success);
        console.log('Response error '+err);
        if(success){
            res.send(201 , job);
            return next();
        }else{
            return next(err);
        }
    });
}

function deleteJob(req , res , next){
    res.setHeader('Access-Control-Allow-Origin','*');
    jobs.remove({_id:mongojs.ObjectId(req.params.jobId)} , function(err , success){
        console.log('Response success '+success);
        console.log('Response error '+err);
        if(success){
            res.send(204);
            return next();      
        } else{
            return next(err);
        }
    })
    
}

var PATH2 = '/items';

server.get({path : PATH2 +'/:itemId' , version : '0.0.1'} , findItem);
server.post({path : PATH2 , version: '0.0.1'} ,postNewItem);

var PATH = '/jobs';

server.get({path : PATH , version : '0.0.1'} , findAllJobs);
server.get({path : PATH +'/:jobId' , version : '0.0.1'} , findJob);
server.post({path : PATH , version: '0.0.1'} ,postNewJob);
server.del({path : PATH +'/:jobId' , version: '0.0.1'} ,deleteJob);


server.listen(port ,ip_addr, function(){
    console.log('%s listening at %s ', server.name , server.url);
});
Test the application with POST and get request.
Read steps below to start both DB and node server first.
Start MongoDB database server first, for this you have to go to your MongoDB bin directory and start(execute) mongod executable.
on MAC machine, command will work like this.
ashish-mac:bin ashish.nigam$ sudo ./mongod
// I used sudo to provide admin access, it depends upon your directory on which mongod resides.

Now you have to start the node server which we created just now, open another terminal window
and from there type in following command (move to project directory).

$ node app.js 
Now you are ready to make post and get request. so open another window and use below mentioned post request.
POST Request:
$ curl -i -X POST -H "Content-Type: application/json" -d '{"title":"NodeJS Developer Required" , "description":"NodeJS Developer Required ashish nigam" , "location":"Sector 30, Gurgaon, India"}' http://127.0.0.1:8080/jobs
GET Request: (Open your browser and use below mentioned IP and get request for all jobs)
http://127.0.0.1:8080/jobs
My Chrome browser image with get request:




Get a specific JOB POST:
http://127.0.0.1:8080/jobs/538c42d9a24b3e1b094e1aa6
Part which is common : http://127.0.0.1:8080/jobs while job id added after jobs.
538c42d9a24b3e1b094e1aa6        is JOB id in my sample request.
To find the get & post request path refer the following code section in app.js
var PATH2 = '/items';

server.get({path : PATH2 +'/:itemId' , version : '0.0.1'} , findItem);

server.post({path : PATH2 , version: '0.0.1'} ,postNewItem);



var PATH = '/jobs';

server.get({path : PATH , version : '0.0.1'} , findAllJobs);

server.get({path : PATH +'/:jobId' , version : '0.0.1'} , findJob);

server.post({path : PATH , version: '0.0.1'} ,postNewJob);

server.del({path : PATH +'/:jobId' , version: '0.0.1'} ,deleteJob);
Thank you for reading my BLOG.
Please share your feedback and any query regarding this blog are most welcome.

Reference Link: