RPG Webservice - GET PUT POST - RPGLE Service Program

Let’s refactor the RPG web service examples (GET, POST, PUT, DELETE) into a clean, modular service program. This approach makes your code reusable, testable, and ready for integration with IWS or CGI wrappers.

Separating the front end (webservice interface logic - ie: IWS or CGI) from the backend (business logic) is a clean way to build your webservices.

BACKEND: Business Logic

RPGLE Service Program: CustomerAPI

We’ll create a service program named CUSTOMERAPI with four procedures:

  • getCustomer
  • createCustomer
  • updateCustomer
  • deleteCustomer

Each procedure handles one HTTP method, performs the logic (get, create, update or delete) and returns a JSON string.

hint: You can learn more about procedures and prototypes in my course on Programming - RPG (Report Program Generator)

Step 1: Define the Prototype (CUSTOMERAPI_PR)

In RPGLE (especially free-form RPG), a prototype is like a function signature, it defines how a program or procedure can be called. Think of it as the "contract" that spells out the name, parameters, and return type of a callable routine, without actually implementing it. 

**FREE
dcl-pr getCustomer varchar(32767);
  custno packed(5:0);
end-pr;

dcl-pr createCustomer varchar(32767);
  jsonInput varchar(32767);
end-pr;

dcl-pr updateCustomer varchar(32767);
  custno packed(5:0);
  jsonInput varchar(32767);
end-pr;

dcl-pr deleteCustomer varchar(32767);
  custno packed(5:0);
end-pr;

For each of these four prototypes you can see:

  • dcl-pr starts the prototype block.
  • xxxCustomer is the name of the procedure.
  • var(32767) is the (optional) return type. In this case we are returning a long string of data (this will be a string of json from each of our procedures)
  • Each parameter is defined with its type and optional keywords like value, const, options(*nopass), etc.
  • end-pr closes the prototype.

Step 2: Implement the Procedures (CUSTOMERAPI)

**FREE
ctl-opt nomain dftactgrp(*no) actgrp(*caller);

dcl-f CUSTFILE usage(*update) keyed;




// ----------------------------------------------
// SUBPROCEDURE - getCustomer
// ----------------------------------------------

dcl-proc getCustomer export;
dcl-pi *n varchar(32767);
  custno packed(5:0) value;
end-pi;

dcl-ds error qualified;
  httpstatus int(10) inz(404);
  message varchar(50) inz('Customer not found');
end-ds;

dcl-ds customer extname('CUSTFILE') qualified;
dcl-s json varchar(32767);

chain custno CUSTFILE;
if %found;
  customer = CUSTFILE;
  DATA-GEN customer %DATA(json) +  %GEN('YAJLDTAGEN': '{ "write to stdout": false }');
else;
  DATA-GEN error %DATA(json) +  %GEN('YAJLDTAGEN': '{ "write to stdout": false }');
endif;

return json;
end-proc;




// ----------------------------------------------
// SUBPROCEDURE - createCustomer
// ----------------------------------------------
dcl-proc createCustomer export;
dcl-pi *n varchar(32767);
  jsonInput varchar(32767) value;
end-pi;

dcl-ds newCustomer qualified;
  custno packed(5:0);
  name varchar(50);
  street varchar(50);
  city varchar(30);
  state char(2);
  postal varchar(10);
end-ds;

input jsonInput %DATA(newCustomer : 'GenInDs');

chain newCustomer.custno CUSTFILE;
if not %found;
  CUSTFILE = newCustomer;
  write CUSTFILE;
endif;

return jsonInput;
end-proc;



// ----------------------------------------------
// SUBPROCEDURE - updateCustomer
// ----------------------------------------------
dcl-proc updateCustomer export;
dcl-pi *n varchar(32767);
  custno packed(5:0) value;
  jsonInput varchar(32767) value;
end-pi;

dcl-ds updatedCustomer qualified;
  name varchar(50);
  street varchar(50);
  city varchar(30);
  state char(2);
  postal varchar(10);
end-ds;

input jsonInput %DATA(updatedCustomer : 'GenInDs');

chain custno CUSTFILE;
if %found;
  CUSTFILE.name = updatedCustomer.name;
  CUSTFILE.street = updatedCustomer.street;
  CUSTFILE.city = updatedCustomer.city;
  CUSTFILE.state = updatedCustomer.state;
  CUSTFILE.postal = updatedCustomer.postal;
  update CUSTFILE;
endif;

return jsonInput;
end-proc;




// ----------------------------------------------
// SUBPROCEDURE - deleteCustomer
// ----------------------------------------------
dcl-proc deleteCustomer export;
dcl-pi *n varchar(32767);
  custno packed(5:0) value;
end-pi;

dcl-ds response qualified;
  httpstatus int(10);
  message varchar(50);
end-ds;

dcl-s json varchar(32767);

chain custno CUSTFILE;
if %found;
  delete CUSTFILE;
  response.httpstatus = 200;
  response.message = 'Customer deleted';
else;
  response.httpstatus = 404;
  response.message = 'Customer not found';
endif;

DATA-GEN response %DATA(json) %GEN('YAJLDTAGEN': '{ "write to stdout": false }');

return json;
end-proc;

Step 3: Compile and Bind

CRTRPGMOD MODULE(MYLIB/CUSTOMERAPI) SRCFILE(MYLIB/QRPGLESRC)

CRTSRVPGM SRVPGM(MYLIB/CUSTOMERAPI) MODULE(MYLIB/CUSTOMERAPI) EXPORT(*ALL)

Step 4: Call from CGI or IWS Wrapper

In your wrapper program (e.g., CGI_CUSTAPI), you can call:

json = getCustomer(123);
json = createCustomer(postBody);
json = updateCustomer(123 : putBody);
json = deleteCustomer(123);

We can even add logging, validation, or authentication next.

Next, let's take this code, enter it using VS-Code for IBM i, review and show it working!

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
>