Security News
Input Validation Vulnerabilities Dominate MITRE's 2024 CWE Top 25 List
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Module loader (plugin system) based on dependency injection for NodeJS applications
Module loader (plugin system) based on dependency injection for NodeJS applications.
Forget the
KlarkJS is a novel system for NodeJS module dependency management that handles the creation of the modules, resolves their internal and external dependencies, and provides them to other modules.
It works with dependency injection based on function parameters in order to define the components dependencies. This architecture decreases dramatically the boiler plate code of an ordinary NodeJS application.
We inspired from 2 main tools:
npm install --save klark-js
Potentially, all the NodeJS modules are isolated plugins. Each plugin depends on other plugin. The other plugins could either be external (from node_modules
folder) or internal (another plugin in our source code tree). Thus, we can simply define a plugin providing the plugin name and the dependencies. The following is a typical KlarkJS module declaration:
KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) {
return {
log: function() { console.log('Hello from module myModuleName1') }
};
});
KlarkModule
is a global function provided by KlarkJS in order to register a module. The module registration requires 3 parameters.
The dependencies of the module are defined by the controller parameter names. The names of the modules are always camel case and the external modules (node_modules
) are always prefixed by $
.
On the following excerpts we depict a simple NodeJS application in pure NodeJS modules and a NodeJS application in KlarkJS. We will observe the simplicity and the minimalism that KlarkJS provides.
/////////////////////
// ./app/app.js
var express = require('express');
var mongoose = require('mongoose');
var config = require('../config');
var db = mongoose.connect(config.MONGODB_URL);
var app = express();
app.get('/', function(req, res){
db.collection('myCollection').find().toArray(function(err, items) {
res.send(items);
});
});
app.listen(config.PORT);
/////////////////////
// ./config.js
module.exports = {
MONGODB_URL: 'mongodb://localhost:27017/my-db',
PORT: 3000
};
/////////////////////
// ./index.js
require('./app/app.js');
/////////////////////
// /plugins/app/index.js
KlarkModule(module, 'app', function($express, $mongoose, config) {
var db = $mongoose.connect(config.MONGODB_URL);
var app = $express();
app.get('/', function(req, res){
db.collection('myCollection').find().toArray(function(err, items) {
res.send(items);
});
});
app.listen(config.PORT);
});
/////////////////////
// /plugins/config/index.js
KlarkModule(module, 'config', function() {
return {
MONGODB_URL: 'mongodb://localhost:27017/my-db',
PORT: 3000
};
});
/////////////////////
// ./index.js
var klark = require('klark-js');
klark.run();
// pure NodeJS
var express = require('express');
var app = express(); ...
// Kark JS
KlarkModule(module, 'app', function($express) {
In pure NodeJS version, when we want to use an external dependency, we have to repeat the dependency name 3 times.
require('express');
var express
express();
In KlarkJS version, we define the dependency only once, as the parameter of the function.
// pure NodeJS
var config = require('../config.js');
// Kark JS
KlarkModule(module, 'app', function(config) { ...
In pure NodeJS version, we define the internal dependencies using the relative path from the current file location to the dependency's file location. This pattern generates an organization issue. When our source files increases, and we want to change the location of a file, we have to track all the files that depends on the this file, and change the relative path. In KlarkJS version, we refer on the file with a unique name. The location of the file inside the source tree does not effect the inclusion process.
The module registration function KlarkModule
forces the programmer to follow a standard pattern for the module definition. For instance, we always know that the dependencies of the module are defined on the controller's parameters. In pure NodeJS the dependencies of the module can be written in many different ways.
var klark = require('klark-js');
klark.run();
Starts the Klark engine. It loads the files, creates the modules dependencies, instantiates the modules.
config
| {Object}
| (optionally)Promise<KlarkAPI>
predicateFilePicker
| Function -> Array<string>
| A function that returns the file path patterns that the modules are located. Read more about the file patterns. Default:predicateFilePicker: function() {
return [
'plugins/**/index.js',
'plugins/**/*.module.js'
];
}
globalRegistrationModuleName
| String
| The name of the global function that registers the KlarkJS modules. Default: KlarkModule
.base
| String
| The root location of the application. The predicateFilePicker
search for files under the base
folder. Default: process.cwd()
.logLevel
| String
| The verbose level of KlarkJS logging. When we want to debug the module loading process, we can yield the logger on high
and observe the sequence of loading. It is enumerated by:
moduleAlias
| Object<String, String>
| Alias name for the modules. If you provide an alias name for a module, you can either refer to it with the original name, or the alias name. For instance, we could take a look on the Default object:moduleAlias: {
'_': '$lodash'
}
When we define the alias name, we can either refer on external library with the name _
or $lodash
KlarkModule(module, '..', function($lodash) {
or '_'
KlarkModule(module, '..', function(_) {
KlarkModule(module, 'myModule1', function($lodash, myModule2, $simpleNodeLogger) {
return {
doSomething: function() { return 'something'; }
};
});
The KlarkJS function controller is the third argument on the KlarkModule registration. The argument names of the controller defines the dependencies.
The internal dependencies should consist of camel case string. The name matches directly the name of the dependency module. In our case, if our argument variable is myModule2
, the KlarkJS will search for the myModule2
module. If the myModule2
does not exists, an error will be thrown.
We define the external dependencies with the prefix $
. This way separates the external with the internal dependencies. The KlarkJS engine translate the real name from the argument and searches on the node_modules to find the package. An error will be thrown if the package does not exists. The name resolution process is the following:
Argument | Real Name |
---|---|
$lodash | lodash |
_ (using alias) | lodash |
$simpleNodeLogger | simple-node-logger |
A KlarkJS module instance is the result of the return value of the controller function. It is somehow similar on the module.exports
. For example, the instance of the KlarkJS module 'myModule1' will be the object:
{
doSomething: function() { return 'something'; }
}
Searches on the internal and the external modules.
name
String
. The name of the module.Searches on the internal modules.
name
String
. The name of the module.Searches on the external modules.
name
String
. The name of the module. String format: external Dependencies.Creates and inserts in the internal dependency chain a new module with the name moduleName
and the controller controller
.
moduleName
String
. The name of the module.controller
Function
. The controller of the module.Promise<ModuleInstance>
Creates and inserts in the internal dependency chain a new module from the file on filepath
.
The content of the file file should follow the KlarkJS module registration pattern.
filepath
String
. The filepath of the file, absolute
path is required. Keep in mind that config.base
contains the application's root folder.Promise<ModuleInstance>
Creates and inserts in the external dependency chain a new module with the name moduleName
.
This module should already exists in the external dependencies (nome_modules)
name
String
. The name of the module. String format: external Dependencies.Returns the internal and external dependencies of the application's modules in a graph form.
{
innerModule1: [
{
isExternal: true,
name: 'lodash'
}
],
innerModule2: [
{
isExternal: false,
name: 'innerModule1'
}
]
}
Access the klark configuration object
We can take advantage of the injection API methods and plug-in modules on the fly. Those modules can either be loaded from a file in the file system, or from a pure JS function.
From the bibliography, there are many ways to structure the unit testing process.
We will Follow a common used pattern that works fine on a large-scale source code tree.
Essentially, we will test each plugin separately. In order to accomplish the isolated testing,
we will create at least one testing file on each plugin. For instance, our /plugins/db/mongoose-connector/
folder could consists from the following files:
index.js
, that contains the functionality of mongoose-connector.index-test.js
, that tests the index.js
functionality.Example of index-test.js
:
var config;
var _;
var expect;
KlarkModule(module, 'configTest', function($chai, ___, _config_) {
config = _config_;
_ = ___;
expect = $chai.expect;
});
describe('config', function() {
it('Should configure a port', function() {
expect(_.isNumber(config.PORT)).to.equal(true);
});
});
Underscore notation
We support the underscore notation (e.g.: config) to keep the variable names clean in your tests. Hence, we strips out the leading and the trailing underscores when matching the parameters. The underscore rule applies only if the name starts and ends with exactly one underscore, otherwise no replacing happens.
Name
For consistency all the unit testing files should postfixed by the -test
name.
If we follow the above pattern, we can easily modify the KlarkJS predicateFilePicker
(@see config), to exclude the -test
files when we run the application,
and include the -test
files when we are testing the application.
klark.run({
predicateFilePicker: function() {
var files = ['plugins/**/index.js'];
if (isTesting) {
files = files.concat('plugins/**/*-test.js');
}
return files;
}
});
npm test
. Runs the tests located on /test
folder, creates a test coverage report on /coverage
folder and send it on coveralls.io.npm run unit-test
. Merely runs the tests.FAQs
Module loader (plugin system) based on dependency injection for NodeJS applications
The npm package klark-js receives a total of 1 weekly downloads. As such, klark-js popularity was classified as not popular.
We found that klark-js demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.
Research
Security News
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.