[ANNOUNCE] node-red-contrib-ts – TypeScript Node

Hi everyone,

This is my first post here, so I’m not entirely sure about the usual way of announcing new nodes – apologies if I’m doing it wrong!

I’ve just released a new Node-RED node: node-red-contrib-ts.
It lets you execute TypeScript directly in your flows with:

:white_check_mark: Monaco Editor (same as VS Code, with IntelliSense)
:white_check_mark: Real-time Type Checking
:white_check_mark: Async/Await Support
:white_check_mark: Two Execution Modes – Fast (Function) or Secure (VM)

Full details on GitHub: GitHub Repository

6 Likes

Looks interesting. One thing though, your code examples aren't TypeScript are they? They appear to be standard modern Node.JS? Might be helpful to put in an example that shows a possible benefit of coding in TS rather than JS. :smiley:

Thanks for the feedback! That’s a great point – the current examples are indeed quite close to modern JavaScript. I’ll add an example that highlights a real benefit of using TypeScript, such as explicit typing and IntelliSense/autocomplete support.
I’ve also just updated the node so the editor now properly analyzes TypeScript during editing, with real-time type checking.

1 Like

Of course, both of these can be dealt with using JavaScript and JSDoc comments. :smiley: So not unique to TS.

Also, I've found some areas of TS IntelliSense is pretty appalling. DOM API's being a particular case in point.

You’re right, JSDoc helps, but TypeScript goes much further:
:white_check_mark: Strict type checking before execution
:white_check_mark: Native support for interfaces and custom types
:white_check_mark: Full interoperability with Node.js and external libraries

With version 1.1.0, the editor now properly analyzes TypeScript with real-time type checking, just like in VS Code. This makes the benefits much clearer in Node-RED.

Here’s a quick example to show the difference:

Without TypeScript (no error detected):

With TypeScript (error detected in real-time):

1 Like

What if you update the core node to add a flag that would enable typescript?

I would add a new tab where users can configure json schemas for the msg and config objects. These schemas are turned automatically into types which are injected in monaco. With this, Users can type msg. and monaco will show them the props available in msg or config objects. Why using schemas and not just types directly? Because typescript alone doesnt validate data at runtime. With json schemas you can run a validation before handing the data over to the node.

Use ajv + typebox instead of zod for runtime validations because it is faster.

You can take a look how runtime validation can be done with ajv here GitHub - AllanOricil/node-red-vue-template: Write Node-RED nodes using Vue and Typescript

Apologies for seemingly being a bit argumentative. I'd just like people to recognise both the strengths and weaknesses of JS vs TS.

Are you running the node through a TS compiler? I know that Node.js is introducing "native" TS execution but I believe that they are "simply" (not actually simple at all I'm sure) removing the type annotations at execution time?

Not sure about interface definitions. Personally, I've always found those to get in the way rather than help but then I have never been a professional JS/TS programmer in an enterprise environment where I think that they might actually be helpful rather than just an overhead.

Custom types can, of course, be defined in JSDoc quite easily.

Would you mind explaining this one? I'm not sure what you mean.

This is certainly easier to do in TS than with JSDoc, that's for sure.

This should work, it works in VS Code:

/**
 * @typedef {object} Page
 * @property {string} path - File path of the page
 * @property {string} name - Generated name of the page (format: "pagesN.jpg")
 * @property {number} width - Width of the page in pixels
 * @property {number} height - Height of the page in pixels
 * @property {number} bytes - Size of the page in bytes
 * @property {string} type - MIME type of the page (always "image/jpeg")
 */

const pages = (msg.payload)
    .filter( l => 1.trim())
    .map( (line, i) => {
        const [path, width, height, size] = line.split(' ; ')
        /** @type {Page} */
        return {
            path,
            name: `pages${i + 1}.jpg`,
            width: Number(width),
            height: Number(height),
            bytes: Number(size),
            type: 'image/jpeg'
        }
    })

pages[0].name = 2

In VS Code it does give the realtime error. But I think it only works there because it needs the ESLINT extensions and config.

Urm, actually, you don't need the type definition, it works without as long as ESLINT is configured correctly.

That seems like an interesting idea.

1 Like

If the Node-RED team wants to integrate this approach into the core function node, they
can reuse my code - it's open source and modular.

users can configure json schemas for the msg and config objects

Excellent idea! I love the approach of JSON schemas → automatic TypeScript types.

Use ajv + typebox instead of zod for runtime validations because it is faster.

I'll study Allan Oricil's GitHub reference and explore how to integrate this cleanly into a configuration tab.

Thanks for this very insightful technical suggestion! :rocket:

1 Like

No worries about being argumentative - these are excellent technical questions!

Maybe add both options? Let users choose between:

  • TypeScript mode (what I have now)
  • JSDoc mode (with ESLINT is configured correctly)

Yes, I'm using TypeScript's transpiler (with cache) to compile TS → JS before execution. The type checking happens at "design time" in Monaco, not runtime.

TypeScript has excellent support for Node.js built-ins and npm packages through @types/* packages. For example: fs, path, crypto etc. are fully typed. Many libraries are coded in typescript, we can copy TS or JS into node script input without conversion.

Thx

1 Like