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.
-
Change the content of main.js to the following code:
var http = require("http");
http.createServer(function (request, response) {
// Send the HTTP header
// HTTP Status: 200 : OK
// Content Type: text/plain
response.writeHead(200, {'Content-Type': 'text/plain'});
// Send the response body as "Hello World"
response.end('Hello World\n');
}).listen(8081);
// Console will print the message
console.log('Server running at http://127.0.0.1:8081/');
-
Execute the command
node main.js
This should prompt Server running at http://127.0.0.1:8081/
in your terminal window.
-
Open the url
http://127.0.0.1:8081/
in your browser.
It should show a Hello World
page.
A short explanation of the code:
-
In the first line the
http
module is included, which provides the functionality
of the http server.
-
Then a server is created that listens to the port 8081.
-
The server provides a function with the parameters
request
and response
.
The response
parameter is used to some text to the webpage that is hosted by the server.
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:
-
In the second line the
fs
module is included, which provides
access to the filesystem.
-
In the function of the server the content of the source file is stored in the variable
data
,
by using the function fs.readFileSync
, that blocks the execution of the code until the
content of the file is read.
-
The function
fs.writeFile
writes the content of the variable data
to
the specified target file asynchronously. This means that this I/O operation does not block the
execution of the server.
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:
-
In the third line the
url
module is included, which allows an
easy parsing of the url parameters.
-
The command
url.parse
writes all url parameters into the variable q
.
The value of an url parameter requested by calling q.<parameter name>
.
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:
-
The path to the directory where the module is installed has to be set to the System variable
NODE_PATH
.
In my case this could be achieved by executing the command
setx NODE_PATH "C:\Program Files\nodejs\node_modules"
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:
-
In the fourth line the
xml2js
module is included, which parses xml files
and writes objects to the xml or the json format.
-
After reading the file content to the variable
data
the function
xml2js.parseString
parses the xml data and converts it into objects that can be
analysed and modified.
-
In this example we modify the content by adding a numbering to the
name
element
of the topic
elements.
-
The command
xml2js.Builder().buildObject
converts the xml objects back to an xml string.
-
The command
JSON.stringify
converts the xml objects to a json string.
The third parameter specifies an intend parameter.
I used two spaces here, but also tabs can be created by using '\t'
.
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
:
-
xmlModifier.js
that modifies the xml content.
-
utilities.js
that encapsulates the file write operation.
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:
-
In the import section there are now
require
statements for the two new files
xmlModifier.js
and utilities.js
, which are located in the same directory as the main.js
.
-
The code in the
xml2js.parseString
function could be strongly reduced by using
the new functions in the separate files.
-
The variable
iFileWritten
and the callback function fileWritten
had to be
introduced to end the server response correctly. (If the response.end()
call
would have been directly after the writeFile
calls, an error would occurr that
response.write
operations are executed after an response.end
.)