Advertisements

Configuration builder in NodeJS with TypeScript

In recent project that I am working on we had a requirement of creating a configuration builder that satisfies following requirements:

  • Ability to read default configuration from a file
  • Ability to read environment specific configuration from a file
  • Ability to override any defaults by command line options
  • Ability to override and/or access any defaults by the set as environment variables
  • Ability to update configuration by any module in the application without modifying default configuration module
  • Ability to read configuration from the database

Some background

The application in which this configuration builder needs to be developed follows a modular approach where each logically grouped functionalities are defined in a module and registers themselves to the main application. A similar approach to that of Angular 1.x modules. Each module receives application object while core app is going through module registration code, so the module can access any application properties that are public. The application is developed in NodeJS and the language of code that we are using TypeScript, so we had the ability to write classes, interfaces, and decorators to simplify code and make it more maintainable.

To put in simple words, we wanted a configuration module which can read arguments, environment variables, configuration files, and database while at the same time provide the ability to any other module in application to easily access and update configuration if required. Other modules will mostly add their configuration options into default config object, at application startup, so other places it can be accessed.

Making configuration builder

configuration_builder

To start with, I created default configuration class, which will do the default configuration read and hold the result in itself. This will then be attached to main Application object as the configuration of the application, every update to configuration will alter this class only.

export default class AppConfig implements IConfig {
    additionalConfigs: Types.Map;

    constructor() {
        this.additionalConfigs = {};
    }

    putExtra(key: string, value: any, override?: boolean): boolean {
        if (this.additionalConfigs[key] && !override) {
            return false;
        }
        this.additionalConfigs[key] = value;
        return true;
    }

    getExtra(key: string): any {
        return this.additionalConfigs[key];
    }
}

IConfig is an interface where I define how default configuration is going to look like. The additionalConfigs and related extra methods were added just in case someone decides not to populate default configuration space and want to add it in additional space then they can use it while updating configuration object.

Once default configuration structure is ready, I defined an interface called IConfigurationBuilder with a method called buildConfiguration that accepts an AppConfig object as an argument. The idea behind this is that if any module wants to contribute in configuration building activity then they can implement this interface, register itself to ConfiurationBuilder and rest will be handled by the builder to make sure that it’s buildConfiguration is executed while configuration was being built.

export interface IConfigBuilder {
    configure(config: AppConfig): Promise<any>;
}

After that, I defined a ConfigurationBuilder, which is responsible for building configuration on application startup by following all above requirements. It will first read default configuration and initialise AppConfig instance, then reads environment files and update configuration object, then read command line options and environment variables and add them to configuration object and finally go through different ConfigurationBuilders registered by different modules and call their buildConfiguration method which updates configuration object based on their requirement. Some default configuration builders we implemented are EnvAndOptConfigReader and DatabaseConfigReader.

export default class ConfigBuilder {
    private builders: Array<IConfigBuilder>;
    private config: AppConfig;

    constructor(configFilePath?: string) {
        this.builders = [];
        if (!configFilePath && fs.existsSync("config.json")) {
            configFilePath = path.join(process.cwd(), "config.json");
        }
        this.config = new AppConfig();
        const customConfig = require(configFilePath);
        _.merge(this.config, customConfig);
    }

    addConfigBuilder(builder: IConfigBuilder): ConfigBuilder {
        this.builders.push(builder);
        return this;
    }

    build(): Promise<any> {
        let defers: Array<Promise<any>> = [];
        this.builders.forEach((builder: IConfigBuilder) => {
            defers.push(builder.configure(this.config));
        });
        return Promise.all(defers).then(() => {
            return this.config;
        });
    }
}

As you can see in the constructor it reads default config.JSON file and initialise AppConfig with it. The `addConfigBuilder` method is supposed to be used by other modules to register their ConfigurationBuilders and finally `build` method loops over all registered builders and waits for them to finish updating configuration object by using deferred promise.

Advantages

Following are some advantages of this:

  • Clean and maintainable code
  • Easy to understand
  • Easy to extend – to add new configuration for a specific module is easy and does not require to update core code
  • Isolation

What else can be done?

In addition to this to make it easier, decorators can be added to the system which will auto-register your class to the builder. Same way decorators for other facilities can also be added like environment specific class loading. To do this you can either use some available auto-discovery plugin for typescript or implement a custom solution using metadata features of a decorator.

Do not reinvent wheel

Search for an available solution before implementing your own, there are some good solutions available online which can do similar stuff. The available solution at this point of time was not sufficient to cover all our application related structure and requirements which led to defining custom solution but for you, you might be able to find readily available solution in NPM repository.

Final note

Above code is just for illustration purpose and not production ready, you need to tweak it and add more validations/checks to make it more suitable for production.

Advertisements

Some useful JavaScript functions

Some JavaScript techniques and functions that will be helpful in web development.

1. Toggling visibility of a table or a div or any other element:

function toggle(div_id) {
      var el = document.getElementById(div_id);
      if ( el.style.display == 'none' ) {  el.style.display = 'block';}
      else {el.style.display = 'none';}
}

2. Remove all child nodes of an element:

var Parent = document.getElementById('elementId');
while(Parent.hasChildNodes()) {
      Parent.removeChild(Parent.firstChild);
}

3. Validate radio group:

function checkradio(formNme,radioGroupName) {
      var radios=document[formNme].elements[radioGroupName];
      for(var i=0;i<radios.length;i++){
              if(radios[i].checked)
                      return true;
      }
      return false;
}

4. Sending an async request to server:

if (window.XMLHttpRequest) {
      xmlhttp = new XMLHttpRequest();
}
else {
      xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.open("POST", url, true);
xmlhttp.onreadystatechange = handleReadyStateChange;
xmlhttp.send(null);
function handleReadyStateChange()  {
      if (xmlhttp.readyState == 4) {
          ///--- Request completed
      }
}

5. To hide/disable scroll bar of a browser:

document.documentElement.style.overflow=document.body.style.overflow='hidden';
// To again enable scroll bar do as follows:
document.documentElement.style.overflow=document.body.style.overflow='auto';

6. Validate url using Regex:

function isValidURL(url){
      //var RegExp = /^(http:\/\/|https:\/\/|ftp:\/\/){1}([0-9A-Za-z]+\.)/;
      var RegExp = /^(http:\/\/){1}([0-9A-Za-z]+\.)/;
      if (RegExp.test(url)) {
               return true;
      }
      return false;
}

7. Swap table columns:

function swapColumns (table, colIndex1, colIndex2) {
      if (!colIndex1 < colIndex2){
            var t = colIndex1;
            colIndex1 = colIndex2;
            colIndex2 = t;
      }
      if (table && table.rows && table.insertBefore && colIndex1 != colIndex2) {
            for (var i = 0; i < table.rows.length; i++) {
                   var row = table.rows[i];
                   var cell1 = row.cells[colIndex1];
                   var cell2 = row.cells[colIndex2];
                   var siblingCell1 = row.cells[Number(colIndex1) + 1];
                   row.insertBefore(cell1, cell2);
                   row.insertBefore(cell2, siblingCell1);
            }
      }
}

8. Tab view:

Refer to this link.

9. Limiting textbox/textarea to number of characters:

Refer to this link.

Character counter in JavaScript

This is about creating a character counter for textarea and limit it accordingly. You may have seen this kinda stuff in some messaging site mostly in sites which provides SMS facilities. They limit us to enter only specified number of characters in text area. Also in some sites there are limitation on characters in comments. So this article will help you in building a similar thing which will look like below picture:

Character counter for text area in JavaScript

Step 1 create a text area for entering text and an input text box which will hold remaining character count.

<DIV>
     Character Remaining:
     <INPUT  disabled size="3" value="140" name="txtLen" id="txtLen">
     <br/>
     <TEXTAREA id="textArea" name="textArea" rows="7" value="" cols="50"></TEXTAREA>
</DIV>

Step 2 Add key events on text area to monitor character count in it:

<TEXTAREA id="textArea" onKeyDown="countChars()" onKeyUp="countChars()" name="textArea" rows="7" value="" cols="50"></TEXTAREA>

Step 3 Create JavaScript function to calculate length of text entered in text area and change input box’s value accordingly and if length goes beyond limit then substring text up to length limit:

function countChars() {
     var l = "140";
     var str = document.getElementById("textArea").value;
     var len = str.length;
     if(len <= l) {
          document.getElementById("txtLen").value=l-len;
     } else {
          document.getElementById("textArea").value=str.substr(0, 140);
     }
}

Step 4 Apply some CSS to it:

DIV{
   PADDING-RIGHT: 5px;
   PADDING-LEFT: 5px;
   FONT-WEIGHT: bold;
   BACKGROUND: #000000;
   PADDING-BOTTOM: 5px;
   MARGIN-LEFT: 0px;
   PADDING-TOP: 5px;
   color:#FFFF00;
}
INPUT{
   BORDER:#000000;
   FONT-WEIGHT: bold;
   FONT-SIZE: 12px;
   COLOR: #000;
   FONT-FAMILY: Tahoma;
   BACKGROUND-COLOR: #FFFF00;
}
TEXTAREA{
   FONT-WEIGHT: bolder;
   BACKGROUND-COLOR: #DDCC00;
   WIDTH:100%;
}

And its done. Hope this helps you.

Create tab view in java script

With the combination of div tag, style:display attribute of div tag and java script one can make a simple tab view which look something like as follow:

Following is the js code for that:

function tabChange(i) {
      for ( var j = 1; j <= 3; j++) {
           if (i == j) {
                document.getElementById("Page" + j).style.display = "block";
                document.getElementById("Tab" + j).className = "tabSelect";
           } else {
                document.getElementById("Page" + j).style.display = "none";
                document.getElementById("Tab" + j).className = "tab";
           }
      }
}

The html code:-

<div>
       <a id="Tab1" href="javascript:tabChange(1)">Tab 1</a><a id="Tab2" href="javascript:tabChange(2)">Tab 2</a><a id="Tab3" href="javascript:tabChange(3)">Tab 3</a>
</div>
<div id="Page1" class="box">
     First tab content.
</div>
<div id="Page2" style="display: none;">
     Second tab content.
</div>
<div id="Page3" style="display: none;">
     Third tab content.
</div>

And the css used in this example:

.tab {
    width: 105px;
    padding: 5px;
    color: #065fba;
    background: #f4f5f8;
    border: #e2e2e2 1px solid;
}
.tab:hover {
    background: #065fba;
    color: #f4f5f8;
}
.tabSelect {
    width: 800px;
    padding: 5px;
    background: #065fba;
    color: #f4f5f8;
    border: #e2e2e2 1px solid;
}
.box {
    width: 600px;
    margin-top:5;
    padding: 5px;
    background: #ffffff;
    border: #065fba 2px solid;
    height:600px;
}
a {
    color: #000000;
    text-decoration: none;
}
%d bloggers like this: