Sunday, October 20, 2013

Creating a Promise from a Node.js style function

Recently I had a situation where I needed to process a web request, write data into three databases and then send a response to the client i.e. success if all three writes succeeded and a failure if even one of them failed.

I wanted keep my code simple and not have to track state across three asynchronous calls. Promises seemed like the natural answer, however it turned out that the MongooseJS API do not return promises (currently).

Fortunately I discovered that the excellent Q library also supports denodefiying calls thus making a standard nodejs API call with call-backs fit into the Promise pattern.

Here’s an example

           /* req is the request passed in by express*/
            var objectA = new ModelA(req.body.objectA);  // instantiate objects from the requests 
            var objectB = new ModelB(req.body.objectB);
             var objectC = new ModelC(req.body.objectC);
           
               // create promise returning functions and bind the methods to the objects
            var saveObjectA = Q.nbind(objectA.save, object;
            var saveObjectB = Q.nbind(objectB.save, objectB);
            var saveObjectC = Q.nbind(objectC.save, objectC);

               Q.all ( [saveObjectA(), saveObjectB(), saveObjectC() ] )
                .then(function (arr) {
                 // The two callbacks for then deal with success and failure respectively
                // success returns an array of arrays. Each internal array lists the item written
     // and number of items written. This is essentially an array of parameters passed to the
    // callback in the original function i.e. before denodeifying

                   res.send(200, {objectAid: objectA.id });
            }, function (err){
                console.error(err);
                 res.send(500); // send a server error

            });

and that's it 

Tuesday, October 15, 2013

MongoDB single user account for multiple databases

Recently, I decided to move the MongoDB backend to an Amazon EC2 instance. Among other things, I needed to set up authentication and this post is about what I learned. While working through this, I didn't find any examples for syntax of the end to end scenario and in fact had to resort to some brute force trial and error - hopefully this post will remedy some of that.

I won't go into too many details about the mechanics of setting up EC2 instances and installing Mongo here though.

Scenario

My app has 6 databases (relatively small) and the NodeJS server runs on a different EC2 instance and accesses the databases over the network. I use MongooseJS to access the MongoDB server.

This translates into the following requirements (among others)

1. Enable authentication for the MongoDB instance.

2. Create user accounts to read-write and administer the databases. I could have chosen to create user accounts per database (relatively easy to setup) but that can be a chore to maintain. Instead I opted to use a single set of user accounts that have access to all the databases (so that password updates etc. are required only once per account)

Steps

The steps below assume a vanilla install of Mongodb that can be accessed without auth over the loopback interface(127.0.0.1). Lines prefixed by # are comments and should be no-ops in a shell if you copy paste but I haven't tested every shell.

  1. Create an admin user

mongo  # fire up the mongo client on the system with MongoDB installed
use admin #switch to the admin database
db.addUser( { user: "adminuser", pwd: "mypassword", roles: [ "userAdminAnyDatabase", "clusterAdmin", "readWriteAnyDatabase" , "dbAdminAnyDatabase" ] } )

Note: The roles give you all access to any database. This is a simplification, and you should scope the roles for each user before you enter production. Read more about roles here

  1. Create database users

Continuing from the previous step, add a database user who can access any database on the system.

db.addUser( { user: "dbuser", pwd: "mypassword", roles: [ "readWriteAnyDatabase" , "dbAdminAnyDatabase" ] } )


  1. Enable auth and network access

On the Ubuntu 12.04 install that I used this meant enabling the following lines (i.e. delete the leading '#') in /etc/mongodb.conf.

port = 27017
auth=true

  1. Setup connection in your app(Node/mongoose)

var myDB = mongoose.createConnection("mongodb://dbuser:mypassword@myipaddress:27017/myfirstdb" ,{auth:{authdb:"admin"}});

The createConnection string should look familiar for the most part. The section {auth:{authdb:"admin"}} is a set of options that tells the driver to authenticate the user (dbuser) against the admin  database instead of myfirstdb.

 You can find more on CreateConnection here

And you are off to the races.

Finer access granularity

In the example above a single user dbuser was given read write access to all databases in that MongoDB instance. In the example below we'll create a database (mydb) and provide access to the user (mydbuser) defined in the admin database. mydbuser  won't have any other access or admin rights to other databases unlike the previous example.


  1. Create the database user

mongo 127.0.0.1:27017/admin -u "adminuser" -p "mypassword"  # logs you in
use admin
db.addUser( { user: "mydbuser", pwd: "mypassword", roles: [ ] } )

Note: You can replace the 127.0.0.1 ip with a public IP and login from a remote system too.

  1. Create the database and assign the user readWrite and dbAdmin roles
Continuing from the previous step

use mydb
db.addUser( { user: "mydbuser", userSource: "admin" , roles: [ "readWrite" , "dbAdmin"] } )


And you now have a user mydbuser with only access rights to the database mydb. Similarly you might divvy out access other databases or other users or both.

One last tip. If you need to login from the mongo shell to access a certain database (while authenticating against admin) use this

mongo myipaddr:27017/mydb -u "mydbuser" -p "mypassword" --authenticationDatabase admin


Monday, July 29, 2013

Promises in AngularJS

Today I was working on the front end and discovered that I had a race condition while fetching data from two URLs.
I was doing something like this
$http.get(firstUrl).success(function(data){
// initialize $scope.data1 and some other work
}
$http.get(secondUrl).success(function(data){
// initialize $scope.data2 and some other work
}
scope.doSomething($scope.data1, $scope.data2);
The results from doSomething were flaky and I realized, I’d been counting on both http.get’s returning in the same order when in case that was not always the case.
To cut a long story short, I discovered the solution was to use AngularJS promises to ensure that both the http operations were completed before the doSomething() function was called. There’s a lot of stuff on the subject on Stack Overflow and other places especially on more complex scenarios but I couldn’t find a nice concise snippet for my very simple case. So here goes
var myCtrl = function ($scope,$http,$window,$q) {
$scope.firstDataPromise = $http({
method:’GET’
,url:$scope.firstDataRecordUrl
});
$scope.secondDataPromise = $http({
method:’GET’
,url:$scope.secondDataRecordUrl
});
$q.all([
$scope.firstDataPromise
,$scope.secondDataPromise
])
.then(function(data){
//data is an array of objects. The first element in the array is result
// of executing the first promise and the second …
$scope.doSomething(data);
});
}