Skip to content

The mighty system command - Part 2

In the first part of this series we got to know the system command, which allowed us to call CL-commands from within the PASE for i environment (IFS shell). In this part we will explore calling system from a JavaScript (Node.js) application and displaying its output in an HTML file.

Those of us who visit the COMMON Europe conferences or their local equivalent will have noticed that Node.js is one of the newest and hottest open source additions to the IBM i platform. Along with Node there's also NPM, the Node Package Manager. It allows one access to the vast ocean of open source JavaScript packages and also helps managing one's dependencies. This means, the IBM i platform can now leverage the digital ecosystem of Node and JavaScript.

(Kerim's Note: While Node is primarily a JavaScript runtime environment, it has become a platform for many other languages. One such language that seems to resonate with IBM i experts more than JavaScript is its superset TypeScript. This article is using JavaScript but most of what's shown here can be ported almost one-to-one to TypeScript.)

And that is exactly what we will do! First I connect to our IBM i via SSH, create a project folder in my home folder (denoted by the ~), and navigate into it.

With npm init -y command, I create a package.json for this folder. This basically creates our project. The -y prevents npm from asking a bunch of project-specific questions. I prefer editing those manually in my text editor later on.

Node Project Creation

Now, if we allow ourselves to connect to our IBM i home folder as a network share, we can open our folder in an editor or IDE. My editor of choice is Microsoft's open source editor Visual Studio Code.

Project in VS Code

Now, what we essentially want to do is call shell commands from within JavaScript. To do so we need the exec method from the child_processes module. As one can see in the Node docs, the exec command requires at least one of three possible arguments, namely the command that is supposed to be called. In addition to that we pass an options object and our callback, which will process the STDOUT output of our shell command.

I create my index.js file and write the following into it:

1
2
3
4
const { exec } = require("child_process");
exec("system wrkactjob", { encoding: "latin1" }, (error, stdout, stderr) => {
  console.log(stdout);
});

In this small example, we're calling the same system wrkactjob command as we did before but this time from within JavaScript. With the following line, we execute our program:

1
gueney@TFSVR1:~/system-befehl $ node index.js

The output should look familiar, which isn't very surprising considering we've done nothing other than wrap our shell command with JavaScript. This wrapper is going to be our building block for the rest of this article.

Kerim's Note: In the example above, you can see that we passed an encoding parameter to format the output to ISO-8859-1. What's also interesting to note is that exec doesn't only pass stdout to our callback but also stderr. If instead there was an error with the exec itself, then the error argument of the callback is filled. This is a common pattern in Node libraries.

Since we want to display our output in the browser, we need a webserver. I am going to use the popular Express webserver library by installing into our project with npm.

1
gueney@TFSVR1:~/system-befehl $ npm install --save express

and create s server.js:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const app = require("express")();
const { exec } = require("child_process");
let port = 8080;

app.get("/wrkactjob", (req, res) => {
  exec("system wrkactjob", { encoding: "latin1" }, (error, stdout, stderr) => {
    res.send(`<pre>${stdout}</pre>`);
  });
});

app.listen(port, () => {
  console.log(`Listening on Port ${port}`);
});

Now when we run our server

1
gueney@TFSVR1:~/system-befehl $ node server.js

we can navigate to the specified route via https://as400:8080/wrkactjob and see the following:

WRKACTJOB in the browser

Finished! In only a few minutes we managed to create a web output and even a Web API for our IBM i CL.

We could now extend our API so that it displays WRKACTJOB with its sbs parameter to filter by subsystem with route like https://as400:8080/wrkactjob/sbs. For that I adjust our server like so:

1
2
3
4
5
6
7
8
9
app.get("/wrkactjob/:sbs", (req, res) => {
  exec(
    `system wrkactjob 'sbs(${req.params.sbs})'`,
    { encoding: "latin1" },
    (error, stdout, stderr) => {
      res.send(`<pre>${stdout}</pre>`);
    }
  );
});

The result:

WRKACTJOB with SBS in the browser

Now there's nothing in our way to create similar API endpoints for other CL commands!

GitHub Repository for this article