Embed Node-RED into mean express on the same port in an iframe

#1

Hello everybody! Thanks for all the great info in this forum!
I have problems whilst trying to embed node-RED in my express mean app in an iframe. I want it to be on the same port to communicate with node-RED and use the node-RED runtime API in my Angular components.
I get RED to start because it logs whats happening in the workflow but I cant access the UI from any adress and neither RED.xyz. It works when I run RED on a different port number but then I have the problem of how to use the runtime API etc.
Ive been on this for several days and its really frustrating. :frowning:
ANY help is extremely appreciated!
Thanks in advance for taking the time!!

0 Likes

#2

What part of NR are you trying to integrate - the user ui or the admin ui?

Also, how have you set things up - you know that you can add NR to an existing ExpressJS server don’t you?

1 Like

#3

Dear Julian,

thanks for your reply! Tbh I’m not sure what the different UIs are I just know the “default” one I get when navigating to an embedded RED on the directory localhost:/red. I’ll google the difference right away.

Im setting it up inside the server like follows (one version of the many I tried. This is just in the express demo app because I wanted to get it to work there first before adding it to the MEAN app), but it only works on a different port than the express server is running, otherwise there will be nothing on /red but 404. I tried quite a lot of different approaches like giving RED just the app instance or giving to it app.getServer and everything else I could think of. But regardless I cant even get to access the runtime API from anywhere else but the RED.start() you see below. Again thanks so much for your help! How can I actually add it to my express server? ( your reply gave me new hope xD )


#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('myapp:server');
var http = require('http');
var express = require("express");

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}



var io        = require('socket.io').listen(server);

var RED = require("node-red");


// Add a simple route for static content served from 'public'
// I tried with and without this --> no difference
app.use("/",express.static("public"));


// Create the settings object - see default settings.js file for other options
var settings = {
    httpAdminRoot:"/red",
    httpNodeRoot: "/api",
    userDir:"/home/nol/.nodered/",
    functionGlobalContext: { }    // enables global context
};

// Initialise the runtime with a server and settings
RED.init(server,settings);

// Serve the editor UI from /red
app.use(settings.httpAdminRoot,RED.httpAdmin);

// Serve the http nodes UI from /api
app.use(settings.httpNodeRoot,RED.httpNode);

// Start the runtime
RED.start();

0 Likes

#4

Did you follow the instructions here: https://nodered.org/docs/embedding

1 Like

#5

Thanks for the reply! yeah I did everything like instructed but this way I can only get RED on a different localhost than my app.

I want it to either

  • run on the same host to access it from angular components etc. and use the runtime api
    or if thats not possible
  • run it on a different host in a iframe and controll it via the api

Thanks for your help! The node-red embedded instructions are very minimal unfortuantely

0 Likes

#6

Sorry, I’m not seeing how that can be the case. The point of the embedding doc is to attach Node-RED to your own instance of Express. You are inserting the RED process as middleware to the Express app instance.

Yes, this is perfectly possible using the embedding instructions.

This should not be needed.

I recommend starting from a clean setup so that you can work through how it all works.

  • Create a new node.js app using mkdir myfolder && cd myfolder && npm init
  • Create server.js using the code from the embedding page. The only thing I would change would be the userDir variable. Change that to be a subfolder of your app. Don’t forget to create the folder of course.
  • Now start the server. You can then edit your flows on http://localhost:8000/red
  • Inside the Node-RED admin ui, add one or more Dashboard nodes and send some data, you should then be able to access the Dashboard page on http://localhost:8000/ui
1 Like

#7

thanks for these instructions! I will try it and let you know!
I REALLY appreciate that you are helping me out here because this is so frustrating and I feel helpless.

1 Like

#8

Any time you want to post some code, please put separate lines before and after the text that contain just 3 backticks (like ```)

This will create a code block that has proper formatting, and avoids having your double quotes become “smart quotes”, which are not usually valid. This makes it easier for us to copy/paste your code into our own environments.

Fell free to edit your previous post – and you can check out this discussion for even more tips.

1 Like

#9

Alright thanks! Ill edit it.

0 Likes

#10

This looks like it should work, the way you have it set up – can you post the startup log information, so we can see what versions are installed and what directories are being used?

Also, I noticed that in your settings you have the home directory as /home/nol/.node-red – do you really have the same initials as Nick O’Leary?

1 Like

#11

I got it to actually work after putting all the following right at the beginning of app.js in the express demo:

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var RED = require("node-red");
var http = require('http');
var embeddedStart = require('node-red-embedded-start');

var app = express();
var server = http.createServer(app);

// Create the settings object - see default settings.js file for other options
var settings = {
    httpAdminRoot:"/red",
    httpNodeRoot: "/api",
    userDir:"/home/nol/.nodered/",
    functionGlobalContext: { }    // enables global context
};

// Initialise the runtime with a server and settings
RED.init(server,settings);

// Serve the editor UI from /red
app.use(settings.httpAdminRoot,RED.httpAdmin);

// Serve the http nodes UI from /api
app.use(settings.httpNodeRoot,RED.httpNode);

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

app.set("view options", {layout: false});
app.use(express.static(__dirname + '/public'));
app.get('/', function(req, res){
    res.render('/views/home.html');
});

module.exports = {app: app, RED: RED};

and all the following in the bin/www

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('myapp:server');
var http = require('http');
var express = require("express");
var RED = require("node-red");
var embeddedStart = require('node-red-embedded-start');

// I know it looks weird lol
var app = app.app;

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

app.use(express.static('public'));

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

// Start the runtime
RED.start().then(embeddedStart(RED, 5)).then((result) => {
    // result is whatever RED.start() resolved to
    // RED.nodes.getFlows() etc are now ready to use
    console.log(RED.nodes.getFlows());
}).catch((err) => {
    if (/^timed out/.test(err.message)) {
        // embeddedStart() timed out
        // the value that RED.start() resolved to is available as err.result
    }
});

and here the log ( versions etc should be the same from when it didnt work because all I changed was where I put some app.use statements


"C:\Program Files\JetBrains\WebStorm 2018.1.4\bin\runnerw.exe" "C:\Program Files\nodejs\node.exe" "C:\Program Files\nodejs\node_modules\npm\bin\npm-cli.js" start

> myapp@0.0.0 start C:\Users\Zeus.Bower\Documents\Repositories\myapp
> node ./bin/www
[Function: route] [Function: use] [Function: path] [Function: enabled] [Function: render] [Function: route] [Function: route] [Function: route]
15 Jun 07:23:40 - [info] 

Welcome to Node-RED
===================

15 Jun 07:23:40 - [info] Node-RED version: v0.18.7
15 Jun 07:23:40 - [info] Node.js  version: v10.3.0
15 Jun 07:23:40 - [info] Windows_NT 10.0.17134 x64 LE
15 Jun 07:23:41 - [info] Loading palette nodes
15 Jun 07:23:43 - [warn] ------------------------------------------------------
15 Jun 07:23:43 - [warn] [node-red/rpi-gpio] Info : Ignoring Raspberry Pi specific node
15 Jun 07:23:43 - [warn] [node-red/tail] Not currently supported on Windows.
15 Jun 07:23:43 - [warn] ------------------------------------------------------
15 Jun 07:23:43 - [info] User directory : /home/nol/.nodered/
15 Jun 07:23:43 - [warn] Projects disabled : set editorTheme.projects.enabled=true to enable
15 Jun 07:23:43 - [info] Flows file     : \home\nol\.nodered\flows_nb-zbower.json
15 Jun 07:23:43 - [warn] 

---------------------------------------------------------------------
Your flow credentials file is encrypted using a system-generated key.

If the system-generated key is lost for any reason, your credentials
file will not be recoverable, you will have to delete it and re-enter
your credentials.

You should set your own key using the 'credentialSecret' option in
your settings file. Node-RED will then re-encrypt your credentials
file using your chosen key the next time you deploy a change.
---------------------------------------------------------------------

15 Jun 07:23:43 - [info] Starting flows
15 Jun 07:23:43 - [info] Started flows

So now I have it working in an iframe in the express demo app ( didnt get it to work in the mean stack angular components yet but that should be doable after seeing it can actually work) but Im now gettig this error and RED loses immediately connection in the iframe and cant reconnect ( lost connection to server).

red.min.js:16 WebSocket connection to 'ws://localhost:3000/red/comms' failed: Connection closed before receiving a handshake response

with the /home/nol/.node-red… no I dont but does it cause problems in the app?

thanks again for your help you two have been amazing already!!

P.S. if ive made any dummy mistakes or ugly stuff like app.app please forgive me as ive been 10h non stop on this just yesterday and my brain stopped working properly at some point :sweat_smile:

0 Likes

#12

Yes, because that’s where NR is expecting all of its data to be. As mentioned previously, you need to change that to the actual userDir folder that contains your NR data for this instance.

1 Like

#13

Ok thanks! Weird enough that it still worked to some degree then!

I’ll try and see if that fixes it.

youre awesome!

0 Likes

#14

I think the folder gets created automatically if the directory is not there yet because i can access flows from every app and even custom nodes. but of course its not a pretty thing so thanks for pointing it out I changed it!

edit: ok i think it indeed messed up something so my apologies

0 Likes

#15

That’s good progress! The only potential issue I see (now that you’ve fixed the home dir) is that nodejs 10.x has not been fully tested. I’ve seen a couple people have problems until they dropped back to v8.3.x LTS, which is what is usually recommended.

1 Like

#16

Another good point! I’ll keep that in mind! As for now I did some more cleanup and got rid of the handshake error and I actually have it fully functional in the express demo app with RED in the iframe loading and working properly. My current issue is now to access the RED instance from outside ( minimal example a button with RED.start() and RED.stop() ). The Express demo app doesnt have webpack etc. so I cant use require/import to have RED in my HTML for the Buttons.

Hence I’ve went back to the MEAN Stack because it has to work with the same underlying concept there too, obviously and I want to use Angular anyways. But I think the routing is messed up. In the express demo app I got access to localhost3000/red not until I put the RED.init() etc. part** in front of every other app.use() statement. But in the MEAN Stack that doesnt help and I’m still left with RED running somewhere in the background, not accessible with localhost3000/red. So I had some small victories but the war is not over :sleepy::sweat:

Thanks again shrickus for the great help and have a fantastic weekend!

** this part (first thing in app.js after imports)


var RED = require("node-red");
var http = require('http');
var embeddedStart = require('node-red-embedded-start');

var app = express();

var server = http.createServer(app);

// Create the settings object - see default settings.js file for other options
var settings = {
    httpAdminRoot:"/red",
    httpNodeRoot: "/api",
    userDir:".node-red",
    functionGlobalContext: { }    // enables global context
};

RED.init(server,settings);

// Serve the editor UI from /red
app.use(settings.httpAdminRoot,RED.httpAdmin);

// Serve the http nodes UI from /api
app.use(settings.httpNodeRoot,RED.httpNode);
0 Likes

#17

still no progress in adding it to a mean stack :frowning:

0 Likes

#18

What exactly do you mean by a mean stack?

The instructions on the site show how to embed Node-RED into your own Express instance. What else do you need to make it a mean stack? I’ve not seen you define that anywhere.

1 Like

#19

Thanks for your reply! Basically long story short I want to have node-RED in an Angular 6 App and to controll it with via the runtime-API from my Angular Components. Ive added it to the express server of the mean (mongo db angular express node.js ) stack and its running on the servers port but I cant excess it from the Angular components yet or display it in an iframe.

Sorry for asing such noob questions but so far all replies helped me a lot and I’m extremely grateful for all the amazing help. I’m just very new to RED and express.

0 Likes

#20

Ok, without seeing your actual code there’s not much we can do to help.

In your previous posts you’ve shared various attempts, such as in this one Embed Node-RED into mean express on the same port in an iframe - but that has two different apps in there and it isn’t clear which you are trying to use.

Your most recent post looks much more like the example from our docs, although I don’t know why you’ve got node-red-embedded-start in there - that isn’t something I recognise.

Nor does it do a server.listen() anywhere, so it isn’t going to do much.

1 Like