Building the RPGLE Webservice Program
Now, craft your RPGLE webservice module: WEBFOOD.
It handles Create (if ID doesn't exist) or Update (if it does), with validation and error handling. We'll use free-format for modernity.
I am going to use IBM BOB (VSCode) for my examples, but you could just as easily do it the old-fashioned way (*shudder) and use SEU/PDM and a source file - Create a new source member: WEBFOOD.RPGLE in QRPGLESRC.
/// ----------------------------------------------------------------------------
/// Program Name: WEBFOOD
/// Description: RESTful webservice program providing CRUD operations for
/// FOODFILE database. Implements GET, ADD, UPD, and DLT functions
/// for managing food inventory ingredients.
///
/// Lesson Introduction:
/// This program teaches the fundamentals of building RESTful webservices in
/// modern RPGLE. You'll learn how to create a backend API that handles all
/// four CRUD operations (Create, Read, Update, Delete) on a database file.
/// The program demonstrates proper parameter handling, database validation,
/// error management, and status messaging - essential skills for building
/// robust webservices that can be consumed by web applications, mobile apps,
/// or other systems. By studying this code, you'll understand how to structure
/// a webservice program, validate input data, perform database operations
/// safely, and return meaningful status messages to the calling application.
///
/// Purpose:
/// - Demonstrate RESTful webservice implementation in RPGLE
/// - Provide backend API for food ingredient management
/// - Show proper CRUD operation handling with validation
/// - Implement error handling and status messaging
///
/// Features:
/// - GET: Retrieve ingredient record by ID
/// - ADD: Create new ingredient with duplicate validation
/// - UPD: Update existing ingredient with existence check
/// - DLT: Delete ingredient with existence validation
/// - Comprehensive error handling and status messages
/// - PCML generation for webservice integration
///
/// Usage:
/// Call with three parameters:
/// 1. function (char 3) - Operation: 'GET', 'ADD', 'UPD', or 'DLT'
/// 2. rtntext (char 100) - Return message/status text (output)
/// 3. data (FoodRec DS) - Data structure containing ingredient info
///
/// Database Schema (FOODFILE):
/// - INGID : Ingredient ID (key field)
/// - INGNAME : Ingredient name
/// - CATEGORY : Food category
/// - MEASURE : Unit of measurement
/// - QUANTITY : Quantity amount
/// - EXPDATE : Expiration date
/// - ORGANIC : Organic flag
///
/// Author: Nick Litten
///
/// Modification History:
/// v.001 2025.10.19 njl - Initial creation for online example
/// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Control Options
// ----------------------------------------------------------------------------
ctl-opt
main(mainline)
optimize(*full)
option(*nodebugio:*srcstmt:*nounref)
pgminfo(*pcml:*module)
actgrp(*new)
indent('| ')
alwnull(*usrctl)
copyright('v.001 - RESTful webservice for FOODFILE CRUD operations');
// ----------------------------------------------------------------------------
// File Declarations
// ----------------------------------------------------------------------------
dcl-f FOODFILE usage(*INPUT:*OUTPUT:*UPDATE:*DELETE) keyed usropn rename(FOODFILE:RECFOOD);
// ----------------------------------------------------------------------------
// Main Procedure: mainline
// ----------------------------------------------------------------------------
Dcl-Proc mainline;
Dcl-Pi *n;
function char(3);
rtntext char(100);
data likeds(FoodRec);
end-pi;
// Data structure externally defined from FOODFILE record format
Dcl-Ds FoodRec extname('FOODFILE':*input) qualified inz;
end-ds;
// -------------------------------------------------------------------
// Main Processing Logic with Error Handling
// -------------------------------------------------------------------
monitor;
// Validate function parameter - must be one of four valid operations
If (function <> 'GET' and function <> 'ADD' and function <> 'UPD' and function <> 'DLT');
rtntext = '(INVALID) Function must be GET/ADD/UPD/DLT: ' + %trim(function);
Else;
// Open the food ingredient file for processing
open foodfile;
// -------------------------------------------------------------------
// Process Request Based on Function Type
// -------------------------------------------------------------------
Select;
// -------------------------------------------------------------------
// GET Operation: Retrieve ingredient record by ID
// -------------------------------------------------------------------
When (function = 'GET');
chain (data.INGID) FOODFILE;
If (%found(FOODFILE));
// Record found - populate return data structure
data.INGNAME = INGNAME;
data.CATEGORY = CATEGORY;
data.MEASURE = MEASURE;
data.QUANTITY = QUANTITY;
data.EXPDATE = EXPDATE;
data.ORGANIC = ORGANIC;
rtntext = 'Row ' + %char(data.INGID) + ' Successfully Read';
Else;
// Record not found
rtntext = '(READ FAIL) Ingredient ID does not exist: ' + %char(data.INGID);
EndIf;
// -------------------------------------------------------------------
// ADD Operation: Create new ingredient record
// -------------------------------------------------------------------
When (function = 'ADD');
// Check if ingredient ID already exists
chain (data.INGID) FOODFILE;
If (not %found(FOODFILE));
// ID is unique - safe to add new record
INGNAME = %trimr(data.INGNAME);
CATEGORY = %trimr(data.CATEGORY);
MEASURE = %trimr(data.MEASURE);
QUANTITY = data.QUANTITY;
EXPDATE = data.EXPDATE;
ORGANIC = data.ORGANIC;
write RECFOOD;
rtntext = 'Row ' + %char(data.INGID) + ' Successfully Added';
Else;
// Duplicate ID - cannot add
rtntext = '(ADD FAIL) Ingredient ID already exists: ' + %char(data.INGID);
EndIf;
// -------------------------------------------------------------------
// UPD Operation: Update existing ingredient record
// -------------------------------------------------------------------
When (function = 'UPD');
// Locate record to update by ingredient ID
chain (data.INGID) FOODFILE;
If (%found(FOODFILE));
// Record found - update all fields
INGNAME = %trimr(data.INGNAME);
CATEGORY = %trimr(data.CATEGORY);
MEASURE = %trimr(data.MEASURE);
QUANTITY = data.QUANTITY;
EXPDATE = data.EXPDATE;
ORGANIC = data.ORGANIC;
update RECFOOD;
rtntext = 'Data Successfully Updated';
Else;
// Record not found - cannot update
rtntext = '(UPDATE FAIL) Ingredient ID does not exist for UPDATE: ' + %char(data.INGID);
EndIf;
// -------------------------------------------------------------------
// DLT Operation: Delete ingredient record
// -------------------------------------------------------------------
When (function = 'DLT');
// Locate record to delete by ingredient ID
chain (data.INGID) FOODFILE;
If (%found(FOODFILE));
// Record found - proceed with deletion
delete RECFOOD;
rtntext = 'Row ' + %char(data.INGID) + ' Successfully Deleted';
Else;
// Record not found - cannot delete
rtntext = '(DELETE FAIL) Ingredient ID does not exist for DELETE: ' + %char(data.INGID);
EndIf;
EndSl;
// Close file after processing
close foodfile;
EndIf;
Return;
// -------------------------------------------------------------------
// Error Handler: Catches unexpected errors during processing
// -------------------------------------------------------------------
on-error;
rtntext = '(ERROR) Main Program Error: ' + %char(%status);
endmon;
end-proc;
Understanding WEBFOOD: A Sample RPGLE Program for Web Services on FOODFILE
You build web services that manage food inventory. This RPGLE program handles CRUD operations on FOODFILE. It reads, adds, updates, or deletes rows like Tomato with INGID 1001. Call it from JavaScript or Postman. Pass a function like 'READ' and data. It returns a message in rtntext.
This is expecting an input JSON PAYLOAD like this:
"function": "ADD",
"rtntext": "status",
"food": {
"ingid": 0,
"ingname": "INGNAME",
"category": "CATEGORY",
"measure": "MEASURE",
"quantity": 0,
"expdate": "DATE",
"organic": "ORGANIC"
}
}
Let's take a stroll through the code and give you an overview of what this program is, and how I want it to work.
Program Header and Options
The top comments list the name WEBFOOD, description, and history. Version 000 dates to October 19, 2025. Our code uses free-format RPGLE.
Control options set the rules. Mainline runs the main procedure. Full optimization speeds it up. No debug I/O keeps output clean. PCML generates XML docs for the module. New activation group isolates it. User control handles nulls. Copyright notes the source.
File Declaration
Declare FOODFILE for input, output, update, and delete. Keyed access uses INGID. Usropn delays open until needed. Rename to RECFOOD avoids clashes.
Main Procedure
Define the procedure interface. It takes function (10 chars), rtntext (100 chars), and data like FoodRec.
Data structure FoodRec pulls fields from FOODFILE: INGID, INGNAME, CATEGORY, MEASURE, QUANTITY, EXPDATE, ORGANIC. Inz zeros it.
dcl-pi *n;
data likeds(FoodRec);
end-pi;
dcl-ds FoodRec extname('FOODFILE':*input) qualified inz;
end-ds;
Monitor Block
I find the code self-explanatory - but if you are complete RPG NOOB then here is a high level overview in human speak 😉
Wrap the whole shebang in a monitor block to snag any runtime gremlins before they turn your pantry into a digital dumpster fire. First off, eyeball the function parameter. If it's not one of our VIPs - READ, ADD, UPDATE, or DELETE - slap an invalid message into rtntext, complete with the offender's name for that extra shaming touch. Otherwise, green light ahead.
Crack open FOODFILE like a fresh cookbook. Then dive into a select statement to route traffic to the right recipe.
For the READ case, chain to the record using data.INGID. Hit paydirt? Copy over the goods from the file fields to your data structure, and now INGNAME slides into data.INGNAME, CATEGORY tags along, and the rest follow suit like obedient sous-chefs. Toast it with a success note in rtntext, something like 'Row 1001 Successfully Read' for our Tomato triumph. No dice? Console the caller with '(READ FAIL) Ingredient ID does not exist: 1001' because nothing says 'try again' like a polite rejection slip.
ADD keeps it simple but sassy. Chain by INGID first to scout for squatters. Empty plot? Plop in the new details from data, giving strings a quick %trimr haircut to ditch trailing spaces (no one likes floppy field lengths). Write out RECFOOD, and celebrate with 'Row 1001 Successfully Added'. But if it's already squatting there, hit back with '(ADD FAIL) Ingredient ID already exists: 1001' eviction denied, folks.
UPDATE plays detective too. Chain by INGID; if the suspect's on-site, refresh the fields from data (trim those strings again, because crisp data is king), then update RECFOOD. Cheer with 'Data Successfully Updated'. Ghosted? '(UPDATE FAIL) Ingredient ID does not exist for UPDATE: 1001' update without a target is just yelling at clouds.
DELETE goes full ninja. Chain by INGID, confirm presence, then delete RECFOOD faster than you can say 'expired yogurt.' Victory lap: 'Row 1001 Successfully Deleted.' Absent? '(DELETE FAIL) Ingredient ID does not exist for DELETE: 1001' can't zap what isn't there, unless you're into phantom deletions.
Wrap up by closing FOODFILE. Manners matter! Now we can return to our caller.
If the on-error clause trips (say, status code 3021 for a missing file, because IBM i loves its error theater), bundle it neatly: '(ERROR) Main Program Error: 3021'. No drama, just facts.
That seals the mainline procedure.
Best Practices & Gotchas
- Trimming: Always %trim() char fields—blanks sneak in!
- Keys: Ensure ID is unique; add unique constraints in SQL for enforcement.
- Scalability: This is row-level; for batches, consider arrays in Part 2.
- Security: Add user auth checks (e.g., via QSYGETPH API) before production.
- Debugging: Use STRDBG on the module; watch %status for clues.
- PCML Magic: Generated XML lets tools like itoolkit auto-map params—preview with DSPPGM WSMAIN.
What's Next?
Compile it into your FOODLIB library:
You've got a battle-tested RPGLE handler!
Next, we'll wrap it in JavaScript endpoints using itoolkitjs, deploy a web server, and test with Postman. We’ll develop a web service endpoint using ExpressJS and itoolkit, enabling interaction with the RPGLE program through RESTful API calls.

