Loading Node.js modules dynamically based on route

If you know where your scripts are, i.e. you have an initial directory, for example DIR, then you can work with fs, for example:

server.js

var fs = require('fs');
var path_module = require('path');
var module_holder = {};

function LoadModules(path) {
    fs.lstat(path, function(err, stat) {
        if (stat.isDirectory()) {
            // we have a directory: do a tree walk
            fs.readdir(path, function(err, files) {
                var f, l = files.length;
                for (var i = 0; i < l; i++) {
                    f = path_module.join(path, files[i]);
                    LoadModules(f);
                }
            });
        } else {
            // we have a file: load it
            require(path)(module_holder);
        }
    });
}
var DIR = path_module.join(__dirname, 'lib', 'api');
LoadModules(DIR);

exports.module_holder = module_holder;
// the usual server stuff goes here

Now your scripts need to follow the following structure (because of the require(path)(module_holder) line), for example:

user_getDetails.js

function handler(req, res) {
    console.log('Entered my cool script!');
}

module.exports = function(module_holder) {
    // the key in this dictionary can be whatever you want
    // just make sure it won't override other modules
    module_holder['user_getDetails'] = handler;
};

and now, when handling a request, you do:

// request is supposed to fire user_getDetails script
module_holder['user_getDetails'](req, res);

This should load all your modules to module_holder variable. I didn't test it, but it should work (except for the error handling!!!). You may want to alter this function (for example make module_holder a tree, not a one level dictionary) but I think you'll grasp the idea.

This function should load once per server start (if you need to fire it more often, then you are probably dealing with dynamic server-side scripting and this is a baaaaaad idea, imho). The only thing you need now is to export module_holder object so that every view handler can use it.


Here is an example of a REST API web service that dynamically loads the handler js file based on the url sent to the server:

server.js

var http = require("http");
var url = require("url");

function start(port, route) {
   function onRequest(request, response) {
       var pathname = url.parse(request.url).pathname;
       console.log("Server:OnRequest() Request for " + pathname + " received.");
       route(pathname, request, response);
   }

   http.createServer(onRequest).listen(port);
   console.log("Server:Start() Server has started.");
}

exports.start = start;

router.js

function route(pathname, req, res) {
    console.log("router:route() About to route a request for " + pathname);

    try {
        //dynamically load the js file base on the url path
        var handler = require("." + pathname);

        console.log("router:route() selected handler: " + handler);

        //make sure we got a correct instantiation of the module
        if (typeof handler["post"] === 'function') {
            //route to the right method in the module based on the HTTP action
            if(req.method.toLowerCase() == 'get') {
                handler["get"](req, res);
            } else if (req.method.toLowerCase() == 'post') {
                handler["post"](req, res);
            } else if (req.method.toLowerCase() == 'put') {
                handler["put"](req, res);
            } else if (req.method.toLowerCase() == 'delete') {
                handler["delete"](req, res);
            }

            console.log("router:route() routed successfully");
            return;
        } 
    } catch(err) {
        console.log("router:route() exception instantiating handler: " + err);
    }

    console.log("router:route() No request handler found for " + pathname);
    res.writeHead(404, {"Content-Type": "text/plain"});
    res.write("404 Not found");
    res.end();

}

exports.route = route;

index.js

var server = require("./server");
var router = require("./router");

server.start(8080, router.route);

handlers in my case are in a subfolder /TrainerCentral, so the mapping works like this:

localhost:8080/TrainerCentral/Recipe will map to js file /TrainerCentral/Recipe.js localhost:8080/TrainerCentral/Workout will map to js file /TrainerCentral/Workout.js

here is a example handler that can handle each of the 4 main HTTP actions for retrieving, inserting, updating, and deleting data.

/TrainerCentral/Workout.js

function respond(res, code, text) {
    res.writeHead(code, { "Content-Type": "text/plain" });
    res.write(text);
    res.end();
}

module.exports = {
   get: function(req, res) {
       console.log("Workout:get() starting");

       respond(res, 200, "{ 'id': '123945', 'name': 'Upright Rows', 'weight':'125lbs' }");
   },
   post: function(request, res) {
       console.log("Workout:post() starting");

       respond(res, 200, "inserted ok");
   },
   put: function(request, res) {
       console.log("Workout:put() starting");

       respond(res, 200, "updated ok");
   },
   delete: function(request, res) {
       console.log("Workout:delete() starting");

       respond(res, 200, "deleted ok");
   }
};

start the server from command line with "node index.js"

Have fun!


app.js

var c_file = 'html.js';

var controller = require(c_file);
var method = 'index';

if(typeof controller[method] === 'function')
    controller[method]();

html.js

module.exports =
{
    index: function()
    {
        console.log('index method');
    },
    close: function()
    {
        console.log('close method');    
    }
};

dynamizing this code a little bit you can do magic things :D