Calling a backend function in a configuration dialogue?

Scenario:
NodeRED sits on a double network, can reach a backend and can be reached by the browser. The browser can't reach the backend directly.

A configuration node has two entries:

  • baseURI (the URI the backend can be reached - not necessarily http(s)
  • category: currently String

FIlling in the values and letting the node do its thing works like a charm. Now the challenge: each backend supports different categories and the "category" input should be replaced with a dropdown list showing the available categories. The backend has such a function.

The basic mechanic using a typeInput and reaching into the backend works:

// Edit snipped
oneditprepare: function () {
      let apiList = getApiList(RED, $('#node-config-input-baseUrl').val());
      $('#node-config-input-api').typedInput(apiList);
    }
// calling backend
const getApiList = (RED, hostname) => {
  console.log(`Retrieve API List for ${hostname}`);
  let options = RED.settings.dominoConnectorApiList;
  return {
    types: [
      {
        value: 'apis',
        options: options
      }
    ]
  };
};

On the NodeRED side:

const apiList = (baseURI) => {
  // HOW can I run this at setup time with the baseURI
  console.log('Running API List');

  // Hardcoded for now
  return [
    { value: 'basis', label: 'Basic CRUD' },
    { value: 'admin', label: 'Domino admin' },
    { value: 'pim', label: 'email, tasks, calendar (PIM)' },
    { value: 'poi', label: 'Office functions' },
    { value: 'setup', label: 'Setup REST' }
  ];
};

module.exports = function (RED) {
  function DominoConnectorNode(config) {
    RED.nodes.createNode(this, config);
    const node = this;
    node.baseUrl = config.baseUrl;
    node.api = config.api;
  }
  RED.nodes.registerType('domino-connector', DominoConnectorNode, {
    settings: {
      dominoConnectorApiList: {
        value: apiList(),
        exportable: true
      }
    }
  });
};

Or am I taking the wrong approach?

If you need the front end (i.e. the Nodes config HTML) to communicate with some backend.
then you need to register an HTTP Admin Endpoint.

Backend

RED.httpAdmin.get('/some-unique-URI-endpoint',RED.auth.needsPermission('flows.read'),
		(request, response) => {
            const myObject = [
                { value: 'basis', label: 'Basic CRUD' },
                { value: 'admin', label: 'Domino admin' },
                { value: 'pim', label: 'email, tasks, calendar (PIM)' },
                { value: 'poi', label: 'Office functions' },
                { value: 'setup', label: 'Setup REST' }
              ]
			response.json(myObject)
		}
	);

Front-End

$.getJSON('some-unique-URI-endpoint', (data) => {
    $('#node-config-input-api').typedInput(data);
})

Ensure you use a unique endpoint identifier (as it can clash with other nodes if to vague)

1 Like

There is another option. But only for something that does not change until Node-RED (re)loads. The register function in the runtime allows passing of some data which can be read in the Editor.

As in this example:

    /** 2) Register the node by name. This must be called before overriding any of the
     *  Node functions. */
    RED.nodes.registerType(uib.moduleName, nodeInstance, {
        credentials: {
            jwtSecret: { type: 'password' },
        },
        // Makes these available to the editor as RED.settings.uibuilderxxxxxx
        settings: {
            // The server's NODE_ENV environment var (e.g. PRODUCTION or DEVELOPMENT)
            uibuilderNodeEnv: { value: process.env.NODE_ENV, exportable: true },
            // Available templates and details
            uibuilderTemplates: { value: templateConf, exportable: true },
            // Custom server details
            uibuilderCustomServer: { value: (uib.customServer), exportable: true },
            // Current version of uibuilder
            uibuilderCurrentVersion: { value: (uib.version), exportable: true },
            // Should the editor tell the user that a redeploy is needed (based on uib versions)
            uibuilderRedeployNeeded: { value: uib.reDeployNeeded, exportable: true },
            // List of the deployed uib instances [{node_id: url}]
            uibuilderInstances: { value: uib.instances, exportable: true },
            // uibRoot
            uibuilderRootFolder: { value: uib.rootFolder, exportable: true },
        },
    })

The properties passed back have to start with the name of the node I believe.

In the example, the uibuilderInstances property is only correct at the time that Node-RED starts since it will change if someone, for example adds or removes a uibuilder node. So it is really a bit superfluous since I have to have an API anyway. I'll remove it one day. But the uibuilderCurrentVersion cannot change when node-red is running.

1 Like

Good to know, I thought RED.settings are only for the runtime - every day is a school day

1 Like

Haha, they are - except when they're not. :grinning:

How very true. And this has prompted me to improve some Editor related code where I turn on debug console output. Used to be if the node-red editor was accessed on localhost, it is now if the NODE_ENV variable on the server was set to "dev" or "development". :slight_smile:

1 Like

A little off-topic, but I apply the same approach in .NET, but with clr "preprocessor directives".

One of my projects in github (.NET), used to try and connect to a server on the host, if a debugger was attached - but had reports that when they use the lib while developing their app - it would timeout.

So, I added a 3rd profile Debug - Local Server that adds preprocessor directives, that is checked before trying to connect to a local server.

#if DEBUG_LOCAL
   // connect to local sever
#endif

meaning they can still debug without connecting to a local server, by using the default Debug profile

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.