Thursday, May 22, 2014

Firebase/NodeJS: interfacing

Firebase/NodeJS: interfacing

solving what problem?

Firebase is a fully scalable, real-time and fully authorised backend system hosted in the cloud. It is amazing for synchronisation of real-time clients with permission-based access across many devices. However, storing relational data in Firebase is not possible and all data must be denormalised. This is fine for many applicatons, but sometimes you just want to interface to your MySQL or MongoDB.

The solution

Here is a solution that allows the best of both worlds. Using Firebase for account management, permission handling and for real-time socket-based synchronisation with any backend system.


Firebase setup:

Firstly let’s setup the permissions on our Firebase, so that users can only post requests to and handle responses from their own Firebase endpoint. This goes in the Security Rules section in your Firebase.

{
  "rules": {
    "users": {
      "$userId": {
        "requests": {
          ".read": "auth != null && $userId == auth.id",          
          ".write": "auth != null && $userId == auth.id"          
        },
        "responses": {
          ".read": "auth != null && $userId == auth.id",
          ".write": "auth != null && $userId == auth.id"          
        }
      }
    }
}

Adding accounts is either done in the Simple Login section of Firebase, or can be done programmatically with the Firebase API. see the Firebase Authentication documentation


Setting up Firebase authentication:

We want a NodeJs app to access our Firebase directly, so we need to generate a Firebase authentication token to use in the app. Fortunately this is a trivial task; simply go to the Secrets section on your Firebase and click show. Keep this safe!


Node app:

Now we are ready to access our Firebase from NodeJS. The following code shows how to connect to the firebase and how to handle requests/responses.

model = require('./model');

var Firebase = require('firebase');
var _ref = new Firebase('https://your_firebase.firebaseio.com/users');
var FirebaseTokenGenerator = require("firebase-token-generator");
var tokenGenerator = new FirebaseTokenGenerator("YOUR_SECRET_KEY");
var TOKEN = tokenGenerator.createToken({user: "node server"},{admin: true});

_ref.auth(TOKEN, function (error) {
    // Log in to Firebase
    if(error) {
        console.log("Login Failed!", error);
    } else {
        console.log("Login Succeeded!");
    }
});

_ref.on('child_added', function (user) {
    // handle requests being added to an account 
    var requests = user.ref().child('requests');
    requests.on('child_added', function(req) {
        handleRequest(req);
    });
});

var handleRequest = function (request) {
    var id, req, func_name, args, res;
    id = request.name();
    req = request.val();

    model_name = req.model;
    var model = global[model_name];
    if(!global.hasOwnProperty(model_name)) {
        request.ref().remove();
        console.log("can't find model: ", model_name);
        return;
    }
    func_name = req.function;
    args = req.args;
    if (func_name) {
        console.log('received request', model_name, func_name, args, request.name());
        if(!model.hasOwnProperty(func_name)) {
            request.ref().remove();
            console.log("can't find function: ", func_name);
            return;
        }
        model[func_name](args, function (data) {
            if (data) {
                res = _ref.child(user).child('responses').child(id);
                res.push(data);
            }
            request.ref().remove();
            console.log('remove request', id);
        });
    }
};

Here is a typical model.js for handling a MySQL request

var mysql = require('mysql');

var db_host = "MYSQL_ENDPOINT";
var db_user = "YOUR_USERNAME";
var db_pass = "YOUR_PASSWORD";

exports.connect = function (database, callback) {
    var connection = mysql.createConnection({
          host: db_host,
          user: db_user,
          password: db_pass
    });
    connection.connect();

    connection.changeUser({database: database}, function (err) {
        callback(connection);
    });
}


exports.list = function (args, callback) {
    var query;

    model.connect('YOUR_DATABASE', function (connection) {
        query = "select * from table_name";
        connection.query(query, function (err, rows, fields) {
            callback(rows);
        });
    });
}

Client-side implementation:

To use this we simply need to set some data on the Firebase … here is some Javascript that could send requests and receive responses.

var _ref = new Firebase('https://your_firebase.firebaseio.com/users');

function s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
                .toString(16)
                .substring(1);
}

function guid() {
    return s4() + s4() + s4() + s4();
}

function request(userId, model_name, func_name, func_args, callback) {
    var _res, id;
    if(userId) {
        id = guid();
        _req = _ref.child(userId).child('requests').child(id);
        _req.set({model: model_name, function: func_name, args: func_args});

        _res = _ref.child(userId).child('responses');
        _res.child(id).on('child_added', function(response) {
            response.ref().remove();
            if(callback) {
                callback(response.val());
            }
        });
    }
}

It can be used like this

request(userId, 'model', 'list', null, function (response) {
    console.log('my data', response);
});

where userId is the Firebase ID and is stored in the login response when you first log into Firebase. Logging in can be seen on the Firebase Authentication documentation.

This code will send a request through Firebase to the NodeJS app, which will handle the request, delete the request from Firebase and post the response. The client will receive the response and remove it from Firebase.

Written with StackEdit.

No comments: