Serving Static Content from a Custom Node (Dynamically update httpStatic

Hi All,

Hopefully the title says it all but when developing custom nodes whats the recommended approach to serving static content in say ./docs? I know httpStatic can be used in settings.js but thats locally set right? Is there a recommended way to dynamically update this?

Thanks

Hi @gazoscalvertos

For this, you will use the resources directory I believe.

It's like httpStatic, but on a per node basis.

I use this to host my own JS files and various resources that is accessed via the browser

https://nodered.org/docs/creating-nodes/resources

To access a file in your resources directory (example docs.html)
http://x.x.x.x:1880/resources/node-red-node-example/docs.html

1 Like

Thanks, I'm using that for static content such as images etc, but thought there might have been a way to have a cleaner URL ie. https://noderedurl/documentation

I'll use the resources dir. :+1:

You can attach your own routes to Node-RED's ExpressJS server if you want to. Just remember to remove them in the close callback otherwise you can end up with multiple instances.

2 Likes

Thanks, with a bit of testing I've managed to figure this out, might not be the most elegant of solutions but here is what works for me:

// grab the node express instance
  const server = RED.httpNode;

  try {
    // set a route to /docs and handle subdirs
    server.get('/docs/:resourcePath(*)', function (req, res) {
      // handle default file ie index.html
      const resourcePath = req.params.resourcePath || 'index.html';
      const filePath = path.join(__dirname, '../docs', resourcePath); 

      // Determine the content type based on the file's extension
      const contentType = mime.lookup(filePath);

      // Read the data from the file 
      fs.readFile(filePath, (err, data) => {
        if (err) {
          // Handle errors, e.g., file not found
          res.status(404).send('File not found');
        } else {
          // Set the content type and send the data in the response
          res.set('Content-Type', contentType);
          res.send(data);
        }
      });
    });
  } catch (error) {
    console.log(error);
  }

You need to be extremely! careful going with this approach.

Once a resource handler has been set - it can't be re-used.

Therefore, someone else developing this method (with the same endpoint) - won't see any issues, until one of their users also has your node installed.

or your users, complaining that they can't get to /docs/ - surprise surprise another node is using this endpoint.

I strongly encourage you to make it unique or use the resources method that is built-in to facilitate this need :grinning:

1 Like

Thanks Marcus, that's a very good point! I'll probably stick to the resources method in that case.

I'll check I don't have anything else which could cause a clash (css etc)

1 Like

I mean if you really needed to do it this way.
Maybe use a prefix to the URI or something to partition it away from other things.

Example: before I started using Resources with my ZWave Node I used to do:
server.get('/zwjs/api/:call', function (req, res) {})
server.get('/zwjs/docs/device/:part', function (req, res) {})
server.get('/zwjs/docs/controller/:part', function (req, res) {})

1 Like

Absolutely you need to make sure that you use unique endpoints. Note how uibuilder goes to some length for example to make sure.

Also, you don't need to manually serve files. You can use Express Static. Serving static files in Express (expressjs.com)

1 Like

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