Contents


Native JavaScript applications on IBM i with Node.js

Modernize your IBM i application suite with JavaScript

Comments

Node.js is an open source project based on Google Chrome V8 JavaScript Engine. It provides a platform for server-side JavaScript applications running without browsers. The event-driven, non-blocking I/O model makes it lightweight and efficient to use. It also provides several built-in modules to simplify programming, especially for networking applications. There are many more third-party modules that can be easily installed with the built-in npm (node packaged modules) tool to extend Node.js.

Note: Node.js is an official trademark of Joyent.

Node.js is now supported for the IBM i platform. The IBM i Open Source Solutions licensed program offering (LPO) 5733OPS has been created for delivery of open source technologies on IBM i. It is included within the Bonus Pack for the IBM i operating system. After being installed, this no-charge LPO is serviced through the 5733OPS Group PTF.

Figure 1. Node.js transcodes JavaScript code into machine code
Node.js transcodes JavaScript code into machine code
Node.js transcodes JavaScript code into machine code

Besides its core functions, Node.js on IBM i also provides the following two IBM i specific extensions:

  • IBM DB2® for i access library that contains JavaScript services that can be called to interact with DB2 for i database objects.
  • Node.js toolkit for IBM i that allows user to access IBM i system resources through XMLSERVICE, such as system status, system values, job information, user spaces, data queues, object information, and so on.
Figure 2. Node.js for IBM i infrastructure
Node.js for IBM i infrastructure
Node.js for IBM i infrastructure

This article introduces the basics of Node.js and describes how to get started on IBM i. It guides you through the setup of Node.js on IBM i and uses some examples to guide you step by step through the process.

Software prerequisites

Node.js for IBM i is packaged as part of 5733OPS which only supports IBM i 7.1 and later. Node.js is delivered in option 10 of this new LPO. In addition to installing the new LPO, some additional software programs are required in order to use Node.js on IBM i.

  • Required licensed programs:
    • 5733OPS, option 10-Node.js v6.x
    • 5770SS1, option 33-Portable App Solutions Environment
    • 5733SC1, option 1-OpenSSH, OpenSSL, zlib
    • 5770DG1,*BASE – IBM HTTP Server for i
  • Optional software required if you need to extend Node.js beyond installed capabilities.
    • Python (5733OPS, option 4) is required by npm to install third-party add-ons.
    • GCC is required if you want to install C/C++ based third-party add-ons through npm.
    • Git (5733OPS, option 7) is required if you want to install third-party add-ons which use Github as the source codes repository through npm

For Python and other open source binaries installation in PASE, refer to the Open Source Technologies online manual and documentation.

Install Node.js on IBM i

Install the 5733OPS option 10 product from the physical DVD media or image files. Then, apply the latest 5733OPS PTF Group to get the latest Node.js support:

  • IBM i 7.1 PTF Group SF99123 – level 3 (or higher)
  • IBM i 7.2 PTF Group SF99223 – level 3 (or higher)
  • IBM i 7.3 PTF Group SF99225 – level 3 (or higher)

After everything is installed successfully, the /QOpenSys/QIBM/ProdData/OPS/Node6 directory will be created.

After the software environment is prepared, perform the following steps to verify the installation:

  1. Start a Qshell session using the following CL command.
    QSH

    Or, start a PASE session with the following CL command.

    CALL QP2TERM
  2. Run following command to make the global links for Node.js runtime. Then you can call Node.js from anywhere.
    /QOpenSys/QIBM/ProdData/OPS/Node6/bin/nodever.sh 6
  3. Run the following commands to verify the Node.js runtime version level. These two commands are located in /QOpenSys/QIBM/ProdData/OPS/Node6/bin.
    node –v

    Output must be the valid Node.js version on the system, such as v6.9.1.

    npm –v

    Output must be the valid npm version on the system, such as 3.10.8.

Figure 3. Node.js for IBM i installation validation
Node.js for IBM i installation validation
Node.js for IBM i installation validation

If the version information looks valid, the installation was successful.

Note: In Qshell sessions, some Node.js applications might throw a signal 5 error. This error is caused by a thread limit of Qshell. Refer to the IBM Knowledge Center documentation for more details.

You can remove the limit by adding environment variable QIBM_MULTI_THREADED with value Y and then restart the Qshell session. Or you can use PASE sessions for your applications.

The following CL command starts a multithread-capable Qshell process.

ADDENVVAR ENVVAR(QIBM_MULTI_THREADED) VALUE(Y)
Figure 4. Enable Qshell for multithread capability
Enable Qshell for multithread capability
Enable Qshell for multithread capability

Overview of the example

The previous sections introduced how to set up the Node.js runtime on IBM i. In the remaining sections, a programming example is introduced to show the capability of Node.js for i. This example creates a web server that allows users to query the DB2 for i database and run CL commands using a web browser.

Note: Node.js does not support source file encoded with EBCDIC. Use a UTF-8 encoding or UTF-8 compatible CCSID, such as 819 (ISO 8859-1 ASCII).

Create a web application using Node.js

All Node.js core functions are supported on IBM i. One of the core features is how easy it is to create a web server using Node.js support for IBM i.

  1. Create a file named sample.js with the following content in any integrated file system directory, for example, /home/njs/sample.js. The following code creates the web server and provides a simple "Hello World" web site. In the following example code, replace IP_Address with the actual IP address of your server and replace Port with the port number to be listened upon by the server.
var http = require('http');
var ip = "IP_Address"; 
var port = Port; 
var webserver = http.createServer((req, res) => { 
  res.writeHead(200, {'Content-Type': 'text/plain'}); 
  res.end('Hello World\n');
});
webserver.listen(port, ip);
console.log('Server running at http://' + ip + ':' + port);
  1. After the file is created and customized, run the JavaScript program to start the web server.
node /home/njs/sample.js

If the following message is displayed, then you know that the web server is successfully running.

Server running at http://IP_address:port/

Now, you can open the browser to access the web server started by Node.js. The sample "Hello World" site is displayed as shown in the following figure.

Figure 5. Verify the web server
Verify the web server
Verify the web server
  1. End the web server.

In this example, the web server is listening to port 8081 to accept the request and to respond. In order to end this web server, we need to end the job manually. To end the web server job, you can press the Esc key within the QSH session and select option 2 to work with the current jobs. Next, end the job running the node program with option 4 as shown in Figure 6.

Figure 6. Manually end the Node.js job
Manually end the Node.js job
Manually end the Node.js job

To automate the Node.js web server administration, customized CL commands can be created to conduct these operations. You can find further information about creating a customized CL command in "Step 9 - Create a customized CL command to start the Tomcat server" of the article, Rev up your Tomcat server on IBM i.

  1. Serve static web pages.

In the previous JavaScript code, the web server is just responding with "Hello World" to any request. Now, we will extend the sample.js program to read content from a static file and respond to the client when the URL, http://<hostname>:port/sample.html, is requested from a browser.

To respond with static web pages to a client, Node.js requires the "fs" file system module and the "url" module to parse the request string. You need to update the sample.js file to include these additional modules. Remember to update the IP address and port values. You can update the following sample.js file with your values:

var http = require('http');
var fs = require('fs');
var url = require('url'); 
var ip = "IP_Address"; 
var port = Port; 
var webserver = http.createServer((req, res) => { 
  var realPath = __dirname + url.parse(req.url).pathname;
  fs.exists(realPath, (exists) => { 
    if(!exists){
      res.writeHead(404, {'Content-Type': 'text/plain'}); 
      res.end("404 Not Found");
    } else {
      var file = fs.createReadStream(realPath); 
      res.writeHead(200, {'Content-Type': 'text/html'}); 
      file.on('data', res.write.bind(res));
      file.on('close', res.end.bind(res)); 
      file.on('error', function(err){
        res.writeHead(500, {'Content-Type': 'text/plain'}); 
        res.end("500 Internal Server Error");
      });
    }
  });
});
webserver.listen(port, ip);
console.log('Server running at http://' + ip + ':' + port);

Note: __dirname is a Node.js internal variable to get the name of the directory that the currently running script resides in.

Then, create a static HTML file named sample.html with the following content and upload it to the same directory that the sample.js file is located in.

<!DOCTYPE HTML> 
<html lang="en-US">
    <head> 
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>Node.js for i Sample</title>
    </head> 
    <style> 
input {
  height:30px;
  border:#ccc solid 1px 
}
input[type="text"] {
  width:500px 
}
input[type="submit"] {
  margin:1em; 
  width:120px 
} 
</style>
    <body> 
        <form name="input" action="query" method="get">
            <div>SQL Command </div>
            <input type="text" name="sql" placeholder="SELECT * FROM ..."/>
            <input type="submit" value="Query"/>
        </form> 
        <form name="input" action="cmd" method="get">
            <div>CL Command </div>
            <input type="text" name="cl" placeholder="WRKSYSSTS"/>
            <input type="submit" value="Run"/>
        </form> 
    </body>
</html>
  1. Re-run the sample.js file.

First, end the running server and then restart the web server again as Step 2 described. Now, we can launch the browser to access the sample page – http://ip:port/sample.html

Figure 7. Serve a static web page
Serve a static web page
Serve a static web page

Although it is just serving a static page for now, the minimal web application framework is ready. You can see how easy it is to get started. There was no additional application server setup needed and no special deployment steps required. In the following sections, the example will be enriched with more JavaScript codes to complete some meaningful functions, running SQL statements, and CL commands.

Accessing DB2 for i data

In this section, the example is enriched to access DB2 for i data using the DB2 for i access library APIs. The web application allows you to run any SQL statement on the web page and display the result.

Warning: The following example demonstrates the capabilities of accessing data through Node.js. In practice, you would not want to externalize a command or SQL interface to a user. The Node.js server is running with the authorities of the user who started the web server.

DB2 for i Extension is a JavaScript API set for DB2 database manipulation on IBM i. It provides JavaScript interfaces corresponding to DB2 for i SQL call level interface (CLI). All the DB2 CLI APIs have been exposed in the extension. The access library is shipped with Node.js for IBM i and is located at: /QOpenSys/QIBM/ProdData/OPS/Node6/os400/db2i/.

To enable the use of the DB2 for i APIs, you only need the db2a.js file in your source code.

var db = require('/QOpenSys/QIBM/ProdData/OPS/Node6/os400/db2i/lib/db2a');

To connect to a database, you need to set the name or the alias name of the database. You can issue the CL command, WRKRDBDIRE, to get the name of target database. In the example shown in Figure 9, the local database name is G0488C55. A special value of '*LOCAL' can be used on the conn() API, if you need to connect to the local database.

Figure 8. Finding the local database name
Finding the local database name
Finding the local database name

Besides the CL command, the database name can also be decided by SQL. You can issue the CL command, STRSQL, to start the SQL interactive session and run the following SQL.

SELECT CATALOG_NAME, CATALOG_STATUS, CATALOG_TYPE FROM QSYS2.SYSCATALOGS

Refer to the following output.

CATALOG_NAME CATALOG_STATUS CATALOG_TYPE
G0488C55 AVAILABLE LOCAL
IASP1 VARYOFF LOCAL
LP21UT24 UNKNOWN REMOTE

Now let's extend the sample.js file to read the SQL statement from the request query string, run the SQL statement, and return the result set to the browser.

var http = require('http');
var fs = require('fs'); 
var url = require('url'); 
var db = require('/QOpenSys/QIBM/ProdData/OPS/Node6/os400/db2i/lib/db2a');
 
var DBname = "*LOCAL"; 
var userId = "UserName";
var passwd = "PassWord";
var ip = "IP_Address"; 
var port = 8080; 
                         
var webserver = http.createServer((req,res) => { 
  var realPath = __dirname + url.parse(req.url).pathname; 
  fs.exists(realPath, (exists) => { 
    if(!exists){ 
      var sql = url.parse(req.url, true).query.sql;
      if(sql && sql.length > 0) { 
        var dbconn = new db.dbconn();  
        dbconn.conn(DBname, userId, passwd);  // Connect to the DB
        var stmt = new db.dbstmt(dbconn);                    
        stmt.exec(sql, (rs) => { // Query the statement
          res.writeHead(200, {'Content-Type': 'text/plain'});
          res.end(JSON.stringify(rs)); 
          stmt.close();  
          dbconn.disconn(); 
          dbconn.close(); 
        });
      }
    } else { 
      var file = fs.createReadStream(realPath); 
      res.writeHead(200, {'Content-Type':'text/html'}); 
      file.on('data', res.write.bind(res)); 
      file.on('close', res.end.bind(res));  
      file.on('error', (err) => { 
        res.writeHead(500, {'Content-Type':'text/plain'}); 
        res.end("500 Internal Server Error"); 
      }); 
    }  
  }); 
}); 
webserver.listen(port, ip); 
console.log('Server running at http://' + ip + ':' + port);

When the source code is updated, end the Node.js program and restart the web server as described in previous sections.

Next, enter the following URL into your browser: http://ip:port/sample.html. The code we added above will enable the SQL function to work. Enter any SQL statement in the first field and click Query. The Node.js server runs the SQL statement against the specified database. For example, the following SQL statement finds the files that grew to the largest size within the last 7 days:

 select SYS_NAME, SYS_ONAME, CURRENT_VALUE from qsys2.syslimits where sizing_name = 'MAXIMUM NUMBER OF VALID ROWS' AND lastchg &gt; current timestamp - 7 days order by current_value desc
Figure 9. Query data from the DB2 database

The output of result set is in JSON format. You can use the JSON.stringify() function to convert it to a readable text, or access the key-value pairs directly.

Figure 10. Result of SQL query
Result of SQL query
Result of SQL query

Access IBM i native objects

The Node.js Toolkit for IBM i API set is based on XMLSERVICE to easily access IBM i native objects, such as PTF information, data queues, and programs. Currently, the following classes are provided by Node.js Toolkit for IBM i.

  • itoolkit – Basic functions and interfaces to call CL commands, QSHELL commands, programs, procedures in service programs and SQL statements.
  • iwork – Interfaces to get system information and job status.
  • iprod – Interfaces to get product and PTF information.
  • iuserSpace – Interfaces to manipulate user space.
  • inetwork – Interfaces to get network information.
  • iobj – Interfaces to get object authorities and information of commands, programs, service programs.
  • idataq – Interfaces to manipulate data queue.

Node.js Toolkit for IBM i is shipped with Node.js for IBM i and is located in the /QOpenSys/QIBM/ ProdData/OPS/Node6/os400/os400/ directory. To use Node.js Toolkit for IBM i, you would require the itoolkit.js file first. The itoolkit.js file connects the XMLSERVICE support with Node.js and is the basis of all the other Node.js Toolkit functions.

var xt = require('/QOpenSys/QIBM/ProdData/OPS/Node6/os400/xstoolkit/lib/itoolkit');

Now let's continue to modify sample.js to use the itoolkit class to run a CL command.

var http = require('http');
var fs = require('fs'); 
var url = require('url'); 
var db = require('/QOpenSys/QIBM/ProdData/OPS/Node6/os400/db2i/lib/db2a');
var xt = require('/QOpenSys/QIBM/ProdData/OPS/Node6/os400/xstoolkit/lib/itoolkit');
 
var DBname = "*LOCAL"; 
var userId = "UserName";
var passwd = "Password";
var ip = "IP_Address"; 
var port = 8080; 
                         
var webserver = http.createServer((req,res) => { 
  var realPath = __dirname + url.parse(req.url).pathname; 
  fs.exists(realPath, (exists) => { 
    if(!exists){ 
      var sql = url.parse(req.url, true).query.sql;
      var cl = url.parse(req.url, true).query.cl;
      if(sql && sql.length > 0) {
        console.log("SQL statement : " + sql);
        var dbconn = new db.dbconn();
        dbconn.conn(DBname, userId, passwd);  // Connect to the DB 
        var stmt = new db.dbstmt(dbconn);
        stmt.exec(sql, (rs) => { // Query the statement
          res.writeHead(200, {'Content-Type': 'text/plain'});
          res.end(JSON.stringify(rs));
          stmt.close();  
          dbconn.disconn(); 
          dbconn.close(); 
        });
      }
      if(cl && cl.length > 0) {
        console.log("CL statement : " + cl); 
        var conn = new xt.iConn(DBname, userId, passwd);
        conn.add(xt.iSh("system -i " + cl)); 
        conn.run((rs) => {
          res.writeHead(200, {'Content-Type': 'text/plain'});
          res.end(xt.xmlToJson(rs)[0].data);
        }); 
      } 
    } else { 
      var file = fs.createReadStream(realPath); 
      res.writeHead(200, {'Content-Type':'text/html'}); 
      file.on('data', res.write.bind(res)); 
      file.on('close', res.end.bind(res));  
      file.on('error', (err) => { 
        res.writeHead(500, {'Content-Type':'text/plain'}); 
        res.end("500 Internal Server Error"); 
      }); 
    }  
  }); 
}); 
webserver.listen(port, ip); 
console.log('Server running at http://' + ip + ':' + port);

Node.js Toolkit for IBM i allows running several commands in a batch. In this example, we just need to retrieve the first result.

Now, refresh the http://ip:port/sample.html page in your browser session. Enter a CL command in the second field and click Run. Node.js Toolkit for IBM i runs the command and returns the results to the browser. In this example, the Display System Status (DSPSYSSTS) command is run to retrieve the current system status.

Figure 11. Running the DSPSYSSTS command using Node.js
Running the DSPSYSSTS command using Node.js
Running the DSPSYSSTS command using Node.js

The following figure shows the output of the CL command.

Figure 12. DSPSYSSTS example output
DSPSYSSTS example output
DSPSYSSTS example output

Summary

JavaScript has been a widely used browser scripting language for a long time. Now, Node.js is enabled on the server-side, which makes it even more useful to IBM i clients. Besides the classic JavaScript API, Node.js provides several built-in modules to easily build modern applications. Refer to Node.js Manual and Documentation for more details about these built-in APIs. The DB2 access library and Node.js Toolkit for IBM i extend the functionality of Node.js even further on IBM i. What's more, there are thousands of third-party modules that can be installed with the npm tool. Enjoy your experience of using Node.js on IBM i.

Resources


Downloadable resources


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=IBM i
ArticleID=998601
ArticleTitle=Native JavaScript applications on IBM i with Node.js
publish-date=05102017