Setting up a simple Node.js application

The following documentation was created based on these instructions.

To host a first webpage I used these instructions.

A short explanation of the code:

A webpage that saves XML files

In this section an application is developed that opens an XML file from the filesystem, which has been specified by a request, and saves it in a JSON format to a new file.

For this application we create a new folder with a main.js file and two subfolders. The first one is called source and contains source files that should be used as input. The second directory is called target where the final result should be written to.

Read and save files from the filesystem

As first step we just read in an xml file with the file name source.xml in the source folder and save it with the same content to the target folder.

For this functionality we need access to the file system, which is realized by including the fs module.

The content of the file main.js is now as follows:
        var http = require("http"); //http server
        var fs = require("fs"); //file system


        http.createServer(function (request, response) {
           // Send the HTTP header
           // HTTP Status: 200 : OK
           // Content Type: text/plain
           response.writeHead(200, {'Content-Type': 'text/plain'});

           var strSourceFileName = "source/source.xml";
           var strTargetFileName = "target/source.xml";
           response.write("Try to copy file " + strSourceFileName + " to " + strTargetFileName + "\n");

           try {
             var data = fs.readFileSync(strSourceFileName, function(err) {
               if (err) throw err;
             });

             fs.writeFile(strTargetFileName, data, function(err) {
               if (err) throw err;
             });
             response.end("File successfully copied!");
           }
           catch (err) {
             console.log(err);
             response.end("[ERROR] " + err);
           }



        }).listen(8081);

        // Console will print the message
        console.log('Server running at http://127.0.0.1:8081/');

      


When the page http://127.0.0.1:8081/ is opened in your browser it should say:
Try to copy file source/source.xml to target/source.xml
File successfully copied!


A short explanation of the code:

Parse and interpret the request

In the next step the request is parsed to determine the source file and the target file.

For this functionality we need access to the request parameters that are given in the url. This is realized by the url module.

The content of the file main.js is now as follows:
        var http = require("http"); //http server
        var fs = require("fs"); //file system
        var url = require("url"); //url helpers


        http.createServer(function (request, response) {
           // Send the HTTP header
           // HTTP Status: 200 : OK
           // Content Type: text/plain
           response.writeHead(200, {'Content-Type': 'text/plain'});


           var q = url.parse(request.url, true).query;
           var strSourceFileName = "source/" + q.source;
           var strTargetFileName = "target/" + q.target;

           response.write("Try to copy file " + strSourceFileName + " to " + strTargetFileName + "\n");
           try {
             var data = fs.readFileSync(strSourceFileName, function(err) {
               if (err) throw err;
             });

             fs.writeFile(strTargetFileName, data, function(err) {
               if (err) throw err;
             });
             response.end("File successfully copied!");
           }
           catch (err) {
             console.log(err);
             response.write("Please make sure that the url parameters source and target are provided.\n");
             response.end("[ERROR] " + err);
           }

        }).listen(8081);

        // Console will print the message
        console.log('Server running at http://127.0.0.1:8081/');

      
After executing node main.js the page http://127.0.0.1:8081/?source=source.xml&target=target.xml can be opened in your browser. It should say:
Try to copy file source/source.xml to target/target.xml
File successfully copied!


A short explanation of the code:

Parse and modify the XML file

In this section the source XML file is parsed, modified and then stored again as XML file as well as JSON file in the target folder.

To achieve this the module xml2js is needed, which is not included in the standard npm installation. So in the command line the following command has to be executed:

npm install xml2js

On Windows I faced the problem that the execution of the command node main.js still could not find the module xml2js. I found the following solution in this Stackoverflow topic:

Finally we can modify the XML file source.xml with the following content:

        <?xml version="1.0" encoding="UTF-8"?>
        
          
            Math
            Learn to calculate
          
          
            German
            Learn to read and write in German
          
          
            Geography
            Learn about countries
          
        
      
The file main.js has to be modified to this content:

      var http = require("http"); //http server
      var fs = require("fs"); //file system
      var url = require("url"); //url helpers
      var xml2js = require("xml2js"); //xml parser + builder



      http.createServer(function (request, response) {
         // Send the HTTP header
         // HTTP Status: 200 : OK
         // Content Type: text/plain
         response.writeHead(200, {'Content-Type': 'text/plain'});


         var q = url.parse(request.url, true).query;
         var strSourceFileName = "source/" + q.source;
         var strTargetFileName = "target/" + q.target;

         response.write("Try to parse and modify file " + strSourceFileName + ".\n");
         response.write("Result is written to " + strTargetFileName + ".\n");
         try {
           var data = fs.readFileSync(strSourceFileName, function(err) {
             if (err) throw err;
           });

           xml2js.parseString(data, function (err, result) {
              if (err) throw err;
              //console.dir( result);

              //modify content
              var iTopicCount = result["topics"]["topic"].length;

                for (i = 0; i < iTopicCount;i++) {
                  //console.log(xmlAsObject["backlog"]["story"][i]);
                  result["topics"]["topic"][i]["name"] = (i+1) +". "+ result["topics"]["topic"][i]["name"];
                }



              var builder = new xml2js.Builder();
              var xmlContent = builder.buildObject(result);

              fs.writeFile(strTargetFileName, xmlContent, function(err) {
                if (err) {
                   throw err;
                }
                else {
                  console.log(strTargetFileName + " written!");
                }
                response.write("XML file successfully written!\n");
              });

              var jsonContent = JSON.stringify(result, null, "  ");
              fs.writeFile(strTargetFileName + ".json", jsonContent, function(err) {
                if (err) {
                   throw err;
                }
                else {
                  console.log(strTargetFileName + ".json written!");
                }
                response.end("JSON file successfully written!");
              });


            });



         }
         catch (err) {
           console.log(err);
           response.write("Please make sure that the url parameters source and target are provided.\n");
           response.end("[ERROR] " + err);
         }







      }).listen(8081);

      // Console will print the message
      console.log('Server running at http://127.0.0.1:8081/');
    


By executing node main.js and calling the url http://127.0.0.1:8081/?source=source.xml&target=target.xml the files target.xml and target.xml.json are generated in the target folder.

The topic elements of the target.xml got a number in front of the name. It finally has the following content:
      <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
      
        
          1. Math
          Learn to calculate
        
        
          2. German
          Learn to read and write in German
        
        
          3. Geography
          Learn about countries
        
      
    
The json file target.xml.json has the following content:
      {
        "topics": {
          "topic": [
            {
              "name": "1. Math",
              "description": [
                "Learn to calculate"
              ]
            },
            {
              "name": "2. German",
              "description": [
                "Learn to read and write in German"
              ]
            },
            {
              "name": "3. Geography",
              "description": [
                "Learn about countries"
              ]
            }
          ]
        }
      }
    
A short explanation of the code:

Refactoring

Finally the program can be made more readable by bundling the methods into several classes. For this I created two further javascript files in the same directory as the main.js: This is the content of xmlModifier.js:
      // xmlModifier.js
      //////////////////////////////
      // PUBLIC METHODS           //
      //////////////////////////////
      module.exports = {
        modifyXmlObject: function(xml) {

          var iTopicCount = xml["topics"]["topic"].length;

          for (i = 0; i < iTopicCount;i++) {
            xml["topics"]["topic"][i]["name"] = (i+1) +". "+ xml["topics"]["topic"][i]["name"];
          }

          return xml;

        }//,
      }

    
It provides a method to modify the xml. As parameter it takes the object representation of the xml, performs the modifictions and returns the result again as object representation.

The content of the file utilities.js is as follows:
      // utilities.js
      var fs = require("fs"); //file system
      //////////////////////////////
      // PUBLIC METHODS           //
      //////////////////////////////
      module.exports = {
        writeFile: function(fileName, fileContent, response, callback) {

          fs.writeFile(fileName, fileContent, function(err) {
            if (err) {
               return callback(err);
            }
            else {
              console.log(fileName + " written!");
              response.write(fileName + " successfully written!\n");
              return callback(null, response);
            }
          });

        }//,
      }
    
It just provides a function to write a file. As parameters the file name, the file content and the response of the http server is taken. It writes a file with the given file name and the given content. The information is logged to the console and to the server response.

As last parameter there is a callback method, which is called after the actions have been finished. This is necessary, because for Node.js I/O operations are executed asynchronously. This means that ending the response has to be handled, after all file operations has been finished, which in this case is realized by callbacks.

The code of the file main.js is now as follows:
      var http = require("http"); //http server
      var fs = require("fs"); //file system
      var url = require("url"); //url helpers
      var xml2js = require("xml2js"); //xml parser + builder


      //project imports
      var xmlModifier = require("./xmlModifier");
      var utilities = require("./utilities");
      var iFileWritten = 0;


      http.createServer(function (request, response) {
         // Send the HTTP header
         // HTTP Status: 200 : OK
         // Content Type: text/plain
         response.writeHead(200, {'Content-Type': 'text/plain'});


         var q = url.parse(request.url, true).query;
         var strSourceFileName = "source/" + q.source;
         var strTargetFileName = "target/" + q.target;

         response.write("Try to parse and modify file " + strSourceFileName + ".\n");
         response.write("Result is written to " + strTargetFileName + ".\n");

         try {
           var data = fs.readFileSync(strSourceFileName, function(err) {
             if (err) throw err;
           });

           xml2js.parseString(data, function (err, result) {
              if (err) throw err;
              //console.dir( result);
              var result = xmlModifier.modifyXmlObject(result);
              var builder = new xml2js.Builder();
              var xmlContent = builder.buildObject(result);
              var jsonContent = JSON.stringify(result, null, "  ");
              utilities.writeFile(strTargetFileName, xmlContent, response, fileWritten);
              utilities.writeFile(strTargetFileName + ".json", jsonContent, response, fileWritten);
              //response.end(); //won't work because of asynchronous IO calls

            });

         }
         catch (err) {
           console.log(err);
           response.write("Please make sure that the url parameters source and target are provided.\n");
           response.end("[ERROR] " + err);
         }

      }).listen(8081);

      // Console will print the message
      console.log('Server running at http://127.0.0.1:8081/');

      function fileWritten(err, response) {
        if (err) {
          response.write(err);
          response.end();
        }
        iFileWritten++;
        if (iFileWritten == 2) {
          response.end();
        }

      };

    
The major changes in this code are: