Create the FOOD Web Server
Now, the magic stage of this game to build a webservice: A Node.js app that calls your WEBFOOD module via itoolkit.
We'll use Express for a simple POST endpoint that accepts JSON payloads, invokes RPGLE, and returns results.
Configure the IBM i Connection
Crack open webfoodapp.js (or whatever you fancy) and set up the iToolkit connection.
Create this file in your IFS - for example I created this in /home/nicklitten/webservice-food/webfoodapp.js
Replace the placeholders with your deets, but keep that password under lock and key.
Let's create an app called webfoodapp.js in our project folder and set up the basic Express server:
const { XMLParser } = require('fast-xml-parser');
// Your IBM i connection – tweak as needed
const connection = new Connection({
transport: 'rest',
transportOptions: {
database: '*LOCAL',
username: 'YOUR_USERNAME',
password: 'YOUR_PASSWORD',
url: 'http://your-ibmi-host:80/cgi-bin/xmlcgi.pgm' // Port 80 for HTTP; 443 for HTTPS
}
});
This bad boy connects via REST to XMLSERVICE.
If you're on HTTPS, slap an 's' on that URL and certs if required.
Spin Up the Express App and Endpoint
Now the meat of this webservice sandwich: Add Express and define your /api/food POST endpoint to the same file.
This slurps the JSON, validates the "ADD" function, maps fields to program params, calls WEBFOOD, and echoes back the status.
const app = express();
const port = 3000;
app.use(express.json()); // Magic for parsing JSON bodies
// The endpoint – handles your incoming JSON
app.post('/api/food', (req, res) => {
const body = req.body;
if (!body || body.function !== 'ADD') {
return res.status(400).json({ error: 'Oi! Expected "function": "ADD" in your JSON' });
}
const food = body.food || {};
const rtntext = body.rtntext || 'status';
// Fire up the program call
const program = new ProgramCall('WEBFOOD', { lib: 'FOODLIB' });
// Input params – match your RPGLE defs
program.addParam({ name: 'ingid', type: '10i0', value: (food.ingid || 0).toString() });
program.addParam({ name: 'ingname', type: '50A', value: food.ingname || '' });
program.addParam({ name: 'category', type: '20A', value: food.category || '' });
program.addParam({ name: 'measure', type: '10A', value: food.measure || '' });
program.addParam({ name: 'quantity', type: '10i0', value: (food.quantity || 0).toString() });
program.addParam({ name: 'expdate', type: '10A', value: food.expdate || '' }); // YYYY-MM-DD, yeah?
program.addParam({ name: 'organic', type: '1A', value: food.organic ? 'Y' : 'N' });
// Output: the status
program.addParam({ name: 'status', type: '100A', io: 'out', value: '' });
// Queue it up and run
connection.add(program);
connection.run((error, xmlOutput) => {
connection.clear(); // Tidy up
if (error) {
console.error('Program call went pear-shaped:', error);
return res.status(500).json({ error: 'RPGLE call failed', details: error.message });
}
// Parse the XML response
const parser = new XMLParser({ ignoreAttributes: false });
const result = parser.parse(xmlOutput);
// Nab the status (tweak path if your XML's quirky)
let status = 'No word back';
if (result.myscript && result.myscript.pgm && result.myscript.pgm.parm) {
const parms = Array.isArray(result.myscript.pgm.parm) ? result.myscript.pgm.parm : [result.myscript.pgm.parm];
const statusParm = parms[parms.length - 1];
if (statusParm && statusParm.$.name === 'status') {
status = statusParm.data || 'Empty status';
}
}
// Bundle and send
const response = { [rtntext]: status };
res.json(response);
});
});
// Kick it off
app.listen(port, () => {
console.log(`Food API endpoint live at http://localhost:${port}/api/food`);
});
Save this file in your Project Folder:

Boom – that's your endpoint.
It expects this JSON:
"function": "ADD",
"rtntext": "status",
"food": {
"ingid": 0,
"ingname": "INGNAME",
"category": "CATEGORY",
"measure": "MEASURE",
"quantity": 0,
"expdate": "DATE",
"organic": "ORGANIC"
}
}
Cheeky Tips to Keep It Shipshape
- Security First: Expose this behind NGINX or Apache with HTTPS. Add API keys via middleware (e.g., express-rate-limit) or JWT for the grown-ups. Never hardcode creds – use env vars.
- Error Handling Polish: Wrap that connection.run in try-catch for async gremlins. Log to a file with Winston if you're scaling.
- Scaling Shenanigans: For high traffic, cluster it with PM2. iToolkit's single-threaded, so watch for bottlenecks on big payloads.
- Debug Mode: Set connection.debug = true; to spew XML – gold for troubleshooting RPGLE hiccups.
- Extend It: Swap "ADD" for "GET" by tweaking params (e.g., output arrays). iToolkit handles lists like a champ.
- Version Vibes: This is Node 18+ friendly; if you're ancient, bump to latest LTS.
There you have it, troops – a JavaScript web service endpoint calling your WEBFOOD RPGLE via iToolkit.
From JSON in, to status out in under 100 lines, and your IBM i's now chatting with the cloud like it's 2025 (wait, it is!).
Time to make sure it works and test it next!
