RPG Webservice - GET PUT POST Example

Web services let your RPG programs talk to the outside world, whether it’s a mobile app, a web portal, or another system. In this lesson, we’ll build a simple Customer API with three endpoints:

  • GET → Retrieve customer data
  • POST → Create a new customer
  • PUT → Update an existing customer

What do we need for this example?

  •  IBM i 7.1 or later
  • Apache HTTP Server running (STRTCPSVR SERVER(*HTTP))
  • YAJL JSON library (Scott Klement’s version)
  • A sample file: CUSTFILE with fields like CUSTNO, NAME, STREET, CITY, STATE, POSTAL
  • RPGLE compiler tools: CRTRPGMOD, CRTPGM, or CRTSRVPGM

Let's look at some example code, you can read and understand the basic idea of what each GET, POST, PUT is doing.

In the next lesson we will get hands on and actually write this code correctly as a service program.

But for now, let's dive in and look at the basics:

Simple Example: Retrieve Input return Response

Let’s build a simple RPGLE CGI program that reads a CUST parameter from the query string and returns a JSON response using YAJL. This example assumes a GET request like:

http://yourIBMiServer.com/cgi-bin/CUSTWS.pgm?CUST=12345

RPGLE CGI Program: CUSTWS.rpgle

This is a RPGLE CGI program that assumes the query string is simple (CUST=12345). getenv('QUERY_STRING') grabs the raw query string from the HTTP GET request. We read in the customer code, display it (it will go to *SYSOPR because its running in batch) and then we send an HTML return code saying "this worked successfully"

NOTE: No error handling this is just to show you the most basic example.

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

dcl-s queryString varchar(1000);
dcl-s custId varchar(20);
dcl-s jsonDoc pointer;
dcl-s errMsg varchar(100);

// Get the QUERY_STRING environment variable
queryString = %trim(getenv('QUERY_STRING'));

// Extract CUST value from query string
// Simple manual parsing (you could use a parser for more complex cases)
if %scan('CUST=': queryString) > 0;
  custId = %subst(queryString: %scan('CUST=': queryString) + 5);
endif;

// Write HTTP header
dsply 'Content-type: application/json';
dsply 'We received customer code : ' + %trim(custID);

// Build JSON response using YAJL
jsonDoc = yajl_buf_open();
yajl_addChar(jsonDoc: 'status': 'success');
yajl_addChar(jsonDoc: 'customerId': custId);
yajl_writeStdout(jsonDoc);
yajl_buf_close(jsonDoc);

*inlr = *on;

Makes sense right?

Now let's look at an example of the same concept, but this time using the custID value to read a Customer Master File and return that value.

GET Example: Retrieve Customer Info

If we start with a webservice that is triggered by the user going to a url something like: http://yourIBMiServer.com/cgi-bin/getcustomer/CUSTWS.pgm?CUST=12345

**FREE
ctl-opt dftactgrp(*no);

// Define the file and rename the record format 
dcl-f CUSTFILE usage(*input);
 
// Define a data structure to hold each record
dcl-ds CustData extname(CUSTFILE) end-ds;

dcl-pr getenv pointer extproc('getenv');
  var pointer value options(*string);
end-pr;

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

dcl-s uri varchar(1000);
dcl-s custno packed(5:0);
dcl-s pos int(10);
dcl-s json varchar(1000);

// Extract customer ID from URI
uri = %str(getenv('REQUEST_URI'));
pos = %scan('/cust/' : uri) + %len('/cust/');
custno = %int(%subst(uri : pos));

// Fetch customer
chain custno CUSTFILE;
if %found(CUSTFILE);
  // Now CustData is populated with the current record
  dsply ('Customer ID: ' + CustData.CUSTID);
  dsply ('Name: ' + CustData.NAME);
  dsply ('Balance: ' + %char(CustData.BALANCE));
  ds_status.httpstatus = 200;
  DATA-GEN CustData %DATA(json) %GEN('YAJLDTAGEN' : '{ "write to stdout": true }');
else;
  DATA-GEN ds_status %DATA(json) %GEN('YAJLDTAGEN' : '{ "write to stdout": true }');
endif;

*inlr = *on;

POST Example: Create New Customer

**FREE
ctl-opt dftactgrp(*no);

dcl-f CUSTFILE usage(*update) keyed prefix('CUST.');

dcl-s json_input varchar(32767);
dcl-s content_len varchar(10);
dcl-s new_custno packed(5:0) inz(999);

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

dcl-ds response qualified;
  httpstatus int(10) inz(201);
  message varchar(50) inz('Customer created');
end-ds;

// Read POST body
content_len = %trim(%str(getenv('CONTENT_LENGTH')));
if %int(content_len) > 0;  json_input = %subst(%alloc(%int(content_len)):1:%int(content_len));
endif;

// Parse JSON
input json_input %DATA(input_cust : 'GenInDs' : *OMIT : 'input_cust');

// Insert into file
cust.custno = new_custno;
cust.name = input_cust.name;
cust.street = input_cust.street;
cust.city = input_cust.city;
cust.state = input_cust.state;
cust.postal = input_cust.postal;

write CUSTREC;

// Output response
DATA-GEN response %DATA(json_input) %GEN('YAJLDTAGEN' : '{ "write to stdout": true }');

*inlr = *on;

PUT Example: Update Existing Customer

**FREE
ctl-opt dftactgrp(*no);

dcl-f CUSTFILE usage(*update) keyed prefix('CUST.');

dcl-s uri varchar(1000);
dcl-s custno packed(5:0);
dcl-s pos int(10);
dcl-s json_input varchar(32767);
dcl-s content_len varchar(10);

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

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

// Extract ID from URI
uri = %str(getenv('REQUEST_URI'));
pos = %scan('/cust/' : uri) + %len('/cust/');
custno = %int(%subst(uri : pos));

// Read body
content_len = %trim(%str(getenv('CONTENT_LENGTH')));
if %int(content_len) > 0;
  json_input = %subst(%alloc(%int(content_len)):1:%int(content_len));
endif;

// Parse JSON
input json_input %DATA(input_cust : 'GenInDs' : *OMIT : 'input_cust');

// Update record
chain custno CUSTFILE;
if %found(CUSTFILE);
  cust.name = input_cust.name;
  cust.street = input_cust.street;
  cust.city = input_cust.city;
  cust.state = input_cust.state;
  cust.postal = input_cust.postal;
  update CUSTREC;
  response.httpstatus = 200;
  response.message = 'Customer updated';
else;
  response.httpstatus = 404;
  response.message = 'Customer not found';
endif;

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

*inlr = *on;

Best Practices

 ✅ Use HTTP status codes: 200 OK, 201 Created, 404 Not Found

🔐 Add authentication headers for security

🧪 Test with Postman or curl

🧱 Keep services stateless and modular

📦 Prefer JSON over XML for REST APIs

What Next?

OK.

So, you got this far and hopefully have browsed those code examples and they make sense. Kind of? Dont panic, let's step through a serious code example, and I will record a little video (or it might become quite big if I start waffling) discussing the key aspects from a new IBM i Programmers perspective.

Jump to the next lesson and "Lets goooo"

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