Inicio » Development » How to generate QR tags with Sage X3
Generación de QR desde 4GL usando Node JS

How to generate QR tags with Sage X3

Share on facebook
Share on twitter
Share on linkedin

Problem faced

It is requested that, from Sage X3, there is the possibility to generate labels containing QR codes to identify items through a scanner..

Proposed solution

The proposed solution is the following: externalise the functionality in a package or bundle programmed in Node JS, a technology that optimises development times and facilitates the work thanks to its wide community and versatility.

Bundle file structure

The files are contained in a directory whose name will be the same as the name of our module, as it incorporates only one. In this example it is “xeax-qr-generator”.

Inside is the “lib” directory, where all the code that will be executed on the server side will be placed.

  • xeax-qr-generator
    • lib
      • services
        • eax-qr-generator-service._js
      • index._js
    • package.json

How 4GL and NodeJS communicate

The way in which 4GL and Node JS communicate is through the “ASYRWEBSER” API, which allows the execution of Javascript functions that are published in the modules belonging to the bundles, through the configuration of the package.json file.

How to configure the “package.json” file

In order to configure the “package.json” file, there are a number of mandatory properties that must be taken into account, namely:

  • “name”: It is important to specify the name of the module, as this will be the way to identify it from 4GL.
  • “module”: It is also necessary to specify the file where the functions to be exposed are located. In this case it is the file “index._js”.
    Nótese que se debe mantener la estructura de árbol sage > x3 > extensions > 4gl-apis >module.
  • “dependencies”: This property holds the dependencies used by the bundle. In this case the qrcode library is used with the version from 1.4.4 to 2.0.0.
  "name": "xeax-qr-generator",
  "version": "1.0.0",
  "description": "Emiral's Sage QR generator",
  "author": "Emiral Freelance Group",
  "sage": {
    "x3": {
      "extensions": {
        "4gl-apis": [
            "module": "./lib/index"
  "dependencies": {
    "qrcode": "^1.4.4"


Exposing the function

The file where the functions to be executed from 4GL are published is the “index._js”, so they must be exported:

"use strict";

 * Endpoint called by 4GL to generate QR codes.
 * @param _ - This param is automatically filled and it's a callback reserved for async functionality
 * @param strData - Parameters to send
exports.generateQR = function (_, qrParams) {
    let res = "";
    try {
        const EAXQRGeneratorService = requireUncached("./services/eax-qr-generator-service");
        const qrGeneratorSvc = new EAXQRGeneratorService();

        // Generating QR code
        res = qrGeneratorSvc.generateQR(_, qrParams);

    } catch (error) {
        res = JSON.parse(error);

    return res;

 * Does the sames as require function forcing to read the file everytime it's accessed.
 * @param module 
 * @returns 
function requireUncached(module) {
    delete require.cache[require.resolve(module)]
    return require(module)


Files ending with the _js extension are intended to be processed by the streamline.js library, which is used by Syracuse because of its simple asynchronous code handling. Like HTTP requests or file reading, QR generation is not immediate; that is why it works asynchronously.

The function generateQR is exported, which receives two parameters:

  • The first is the callback function used by the streamline.js library, which must be passed as a parameter to the functions that support it (such as the then of promises). In this way, when the result is obtained, 4GL will be notified.
  • “qrParams”: The JSON containing the desired configuration for the QR code generation.

What the function does is to delegate the QR generation functionality to the service called “”.

Speeding up development

The functionality of the input file “index” has been separated for time optimisation and development convenience. By isolating it, the programmer can read the file from its location and not from the cache.

All Node JS packages are read and stored in memory when the Syracuse server is started. Reads from the filesystem can be forced by removing the cached data, as seen in the “requireUncached” function, which once removed from the cache, and calling the “require” function is forcing the server to fetch it from the physical path.

This type of read should no longer be done in production deployments for performance reasons.

QR code generation

As mentioned before, use is being made of the qrcode library. This library allows to configure the way it is generated in a simple way.

In this example, a specific format of the parameters has been created to meet the needs of the development:

  • “strData”: Content to be encoded.
  • “quality”: Quality of the image to be generated (defined on the library page). Default is high (“H” for “high”).
  • “width”: Size of the side (height/width) of the image to be generated.
  • “scale”: Pixel density per module (the atomic unit that makes up the QR code).

The library in question is imported and thanks to the “toBuffer” function we can perform the asynchronous generation of the QR code. To obtain the result it is important to collect it in a variable and place to the right of the same the function that generates the promise, followed by “.then(_,_)”, which makes that when programming it is done in a similar way to the synchronous (similar to the not so modern async/await). As mentioned before, this is thanks to the streamline.js library.

Once the buffer is obtained, it is encoded in base64 to be processed by 4GL.

module.exports = class EAXQRGeneratorService {
    constructor() {

    generateQR(_, qrParams) {
        const strData = qrParams.strData;
        const quality = qrParams.quality || 'H';
        const width = qrParams.width;
        const scale = qrParams.scale;

        var QRCode = require('qrcode');

        let qrBuffer = QRCode.toBuffer(strData, {
            errorCorrectionLevel: quality,
            width: width,
            scale: scale
        }).then(_, _);
        const result = qrBuffer.toString('base64');

        return result;

JavaScript function execution from 4GL

For this example, a script programmed in 4GL has been developed.

First, the parameters to be passed are stored in a variable of type clob in JSON format.

The EXEC_JS function of the ASYRWEBSER api is executed by passing it the following parameters:

  1. Module (MODULE): Entire path from the package name to the input file.
  2. Function (FONCTION): Desired function, published in the exposed module or file (index).
  3. Mode (MODE): To execute asynchronously and for X3 to embed the callback in the parameters of the published function.
  4. Parameters (DATA): Parameters to be passed to the published function.
  5. Base 64 input (B64_IN): Not applicable, pass “0”.
  6. Callback Position (CALLB): Indicates the position in the parameters where to inject the callback function. In the example it is 0 to be passed as the first parameter..
  7. Return (RETURNS): Used to specify the property of the object to extract. In this case, as it is not a JS object, but a string, there is no need to pass “”.
  8. Base 64 output (B64_OUT): Not applicable, pass “0”.
  9. Response headers (RESHEAD): Not applicable, leave blank.
  10. Response body (RESBODY): Variable to be filled with the result emitted by the bundle function. In this example it will contain the base64 code of the image associated with the text passed in the “Test text” parameters.
Local Clbfile DATA
DATA = '{'
    Append DATA, '"strData": "Texto de prueba"' # QR Text
    Append DATA, ',"quality": "H"' # L, M, Q, H - (L = Low, H = High [default])
    Append DATA, ',"width": null' # Width of image
    Append DATA, ',"scale": null' # Number of pixels per module (4px by default)
Append DATA,'}'

Local Clbfile RESHEAD
Local Clbfile RESBODY
Local Integer STATUSCODE

### Module, function and element to be returned
Local Char MODULE(100) : MODULE = 'xeax-qr-generator/lib/index'
Local Char FONCTION(30) : FONCTION = 'generateQR' #'getUserProfile'
Local Char RETURNS(50) : RETURNS = ""#default"

### The function is asynchronous
Local Char MODE(4) : MODE = 'wait'

### The callback argument is located in the first place for this function
Local Integer CALLB : CALLB = 0

### No Base 64 in or out coding, the arguments can remain empty
Local Char B64_IN(20): B64_IN = "0"
Local Char B64_OUT(20): B64_OUT = "0"

### No additional arguments

# Let's call the function






Leave a Reply

Your email address will not be published.