Ext Direct with NodeJS

Update April 11, 2013
An official npm module supported by Sencha can be found at: https://npmjs.org/package/extdirect

Introduction

The Sencha framework provides a multitude of data connection opportunities to connect a client side application to the server side. The framework introduces a language agnostic technology to access remote server side methods called Ext Direct.

There are several supported server-side environments including PHP, Java, .NET and others however conspicuously missing from the list of server stacks was Node.JS as a backend technology. Some quick research showed a couple of sample implementations utilizing Node.JS, however these examples used older versions of ExtJS and were already a couple of years old. I’ve included some of the articles I used for inspiration while going through this exercise.

I thought it would be an interesting exercise to implement Ext Direct utilizing Node.js and document the experience. To make this interesting I utilized some of the more common modules and node frameworks used in node.js web applications such as Express 3.0 and Sequelize.

There are obviously a ton of alternative yet valid opinions, methods and techniques on how to properly configure or setup a web application so feel free to use this as either an example or inspiration to implement your own Ext Direct application using Node.JS.

Ext Direct

To implement Ext Direct, there are 3 items to consider:

  • Configuration – Identify the objects and methods to be exposed to the client.
  • API – From the configuration, send the client side the api.
  • Router – Route requests to the correct server side elements.

Configuration and API

For this exercise, the configuration is stored in the configuration.js file. Although the specification from the Ext Direct documentation cleanly separates the configuration from the api, for simplicity I’ve merged the configuration and api which will ultimately be sent to the client.

lib/configuration.js

 var self = module.exports = {

 	'api' : {
 		type: 'remoting', 
 		  url: '/direct',
 		  actions: { 
 			   User: [ 
 			     {name: 'create', len: 1},
 			     {name: 'getAll', len: 1},
 			     {name: 'update', len: 1},
 			     {name: 'destroy', len: 1}
 			     ]}
 		}


  }

In this case we will be exposing 4 methods to the client side. Using Express, we will define a route to point to the api, and send the javascript containing the assignment of the api to a client side variable we can use with ExtJS.

controllers/direct.js


exports.api = function(req, res, next){

	// generate the local variable and assign the configured api to the client
	var api = 'Ext.app.REMOTING_API = n';
	var config = require('../lib/configuration.js');
	res.set('Content-Type', 'application/javascript');
	res.send(api + JSON.stringify(config.api));

};

Router

The router is the server side component which receives and processes requests from the client. In the case of Ext Direct, the router will receive a POST from the client which contains transaction data. From the Sencha site, the transaction can contain the following elements (from the Ext Direct spec):

  • action – The class to use
  • method – The method to execute
  • data – The arguments to be passed to the method – array (Ext Direct will support named arguments in the future and therfore an object literal here)
  • type – “rpc” for all remoting requests
  • tid – Transaction ID to associate with this request. If there are multiple transactions in a single POST these will be different.
  • Form posts will contain the following parameters.
  • extAction – The class to use
  • extMethod – The method to execute
  • extTID – Transaction ID to associate with this request. Form POSTs can only support a single transaction
  • extUpload – (optional) field is sent if the post is a file upload
  • Any additional form fields will be assumed to be arguments to be passed to the method.

Express Middleware

Express is built upon Connect which allows middleware. A request to a specific destination can be intercepted and passed to custom handlers. In this case, the remoting URL is set to ‘/direct’ and handled by a specific handler (directhandler in this case). Earlier in the application setup, I created an ORM using Sequelize and stored the model definitions inside the models object. I pass this to the direct handler so that I will have access to all the functionality and data connectivity within the models.

app.js

var directhandler = require('./lib/direct');
app.use('/direct', directhandler('on', models));

Direct Handler

To make this implementation a little more generic, I used a closure to wrap the method calls on the models in the request. So in principle, if you had many models which used the same functionality (which you see in a lot of CRUD forms), you could write 4 simple methods to handle the bulk of the work in a very DRY fashion. In the code snippet below, you see the direct handler, with only the getAll method implemented. (complete example will be posted on github)

/lib/direct.js


var enabled = true;

module.exports = function(onoff, models) {

	enabled = (onoff == 'on') ? true : false;

	return function(req, res, next) {
		if (enabled) {
 
            // get the corresponding object from models
            var model = models[req.body.action];
            var action = api[req.body.method];  //create the closure
            
            if (action !== undefined && typeof action === 'function'){
                var a = action(model,req,res,next);
                a();
            }else{
                console.log('api call is undefined');
                res.end('Undefined');
            }
        }
    }

 };


var api = {

 getAll: function(model, req, res, next){

    // closure must return the inner function
    return function(){

        console.log('in ths function');
        model.findAll({})
        .success(function(data){
            var response = {};
            response.type = 'rpc';
            response.tid = req.body.tid;
            response.action = req.body.action;
            response.method = req.body.method;
            response.result = data;
            res.json(response);

        });


    }

}

};

Client Side

Using Ext Direct on the client side is relatively simple. First thing we need to do is import the API and setup the Ext.direct.Manager. In the application html page which is generated, I added a call to pull down the API. Note the script tag which pulls the /api

/index.ejs

<html lang="en">
    <head>
        <title><%= title %></title>

        <link href="/javascripts/extjs/resources/css/ext-all.css" rel="stylesheet" type="text/css">

        <script src = "/javascripts/extjs/ext-all-debug.js"></script>
        <script type="text/javascript" src="/api"></script>
        <script type="text/javascript" src="ui/app/app.js"></script>

    </head>
    <body>
        
    </body>
</html>

Next, in the Ext launcher, you add the api to the direct manager:

/ui/app.js


Ext.require('Ext.direct.*');

Ext.onReady(function() {
  // add the API to the direct manager
  Ext.direct.Manager.addProvider(Ext.app.REMOTING_API);
});

Ext.Loader.setConfig({
    enabled : true
});

Ext.application({
    name: 'UI',
    appFolder: 'ui/app',

    autoCreateViewport: true,

    requires: ['Ext.data.proxy.Direct'],
    models: ['User'],
    views: ['user.List'],

    controllers: [
    'Users'
    ],
    

    launch: function(){
       
    }

});


Finally you can define your proxy (I do this as part of my Store) with the appropriate method calls:


Ext.define('UI.store.Users', {
    extend: 'Ext.data.Store',
    model: 'UI.model.User',
    autoLoad: false,
    
    proxy: {
        type: 'direct',
        api: {
            create: 'User.create',
            read: 'User.getAll',
            update: 'User.update',
            destroy: 'User.destroy'
        },
        reader: {
            type: 'json',
            //root: 'data',
            successProperty: 'success'
        }
    }
});

File Uploads

Most regular requests are sent as a JSON-Encoded raw HTTP POST. File uploads however are sent as a form POST. This particular example does not implement file uploading yet, but if there is enough interest, send me a pull request on github!

Conclusion

Ext Direct, implemented in Node.JS using Express 3.0 and Sequelize provide a powerful back end technology stack for building highly scalable, high performance web applications. Watch this space for more interesting blog articles!

Available to help your team or project:

As always, if you need help with your project, feel free to contact me or the team at Bannockburn. We would love to help you and your project be successful!

Source

Here is the complete source example on github for your review and would love to get some feedback!

Contact

Connect with me on LinkedIn

or Contact Us

References

ExtDirect Gridpanel w/ Connect on NodeJS
Ext Direct Manager docs
Ext Direct Specification

Google+