This can be fairly personal. You will be developing for a very complex Node.js app so you will want to be somewhat familiar with Node.js, npm and, of course, JavaScript. You will want a good IDE - VSCode is almost certainly the best of breed right now. And you would be well advised to make sure you have a decent configuration for ESLint and Git/GitHub.
There is a simple starter node in the Node-RED source. Personally, I don't like the approach many people take and so I use a somewhat deconstructed method that, I think, allows much clearer understanding and parsing of the complex timings and interactions that your nodes have to deal with. I use this repo to test out my ideas: GitHub - TotallyInformation/Node-RED-Testbed: A blank canvas to test out ideas for Node-RED custom nodes. One of the benefits of this approach that I've found is that it makes it really clear in the runtime code what happens when - e.g. at node-red startup, at the deployment of a node to a flow and at the time a node instance receives a message. These things are complex and interlocked. Being able to see them more clearly saves a lot of head-scratching (it does for me anyway).
But there are plenty of other ideas and approaches. Node.js is nothing if not flexible.
Here is another toolchain from a member of this forum: 🎉 nrg-cli v1.2.1 released - #2 by AllanOricil
How you approach things rather depends on your experience, worldview and whether you will be working with others.
Yes, node.js is great for this. If you need to pick up 3rd-party packages, most people will have published to npm and you can very easily include such packages either for runtime use or for develop-time. Check out a few package.json files to get a feel.
If it is JavaScript code, you will generally want to move that code into your own node. If it consists of Node-RED flows, you may need to re-engineer the logic into node.js modules.
It should be accessible to your custom node. However, not everything has to be directly in your node's code. For runtime code, you can use node.js modules and indeed your should do that. Check out node-red-contrib-uibuilder for a fairly extreme example. The main uibuilder node in particular makes use of around 11 other library modules of my own writing and around 6 external 3rd-party modules. (and a LOT more external modules for developing, not for running).
Your nodes exist in 2 contexts though and you should remember that. The runtime code (*.js) does the heavy lifting during operation. The *.html Editor code defines how your node looks and behaves in the Node-RED Editor. Even the editor code does not have to all live in a single html file though (even though many people do write nodes that way). Check out the testbed nodes I shared above to see how you can easily minimise what is in your node's html file to make it much easier to manage common code (shared across multiple nodes) and move some of the different parts of the code (namely the javascript and the help info) into separate files.
In general, it is much easier - especially as nodes get more complex over time - to keep things split out into logical files.
This one is easy. Because a node or collection of nodes exists in a package. Your package is defined by a package.json file in the root of your repo.
Typically, as you finish a release, you push any final changes to GitHub and create a GitHub release. Then you publish that release using the npm tool to the npm library.
If you include a tag in your published release called "node-red", it should turn up in the flows library and the Node-RED palette manager for people to install.
A gazillion console.log messages!
But for when you get really stuck/confused, the node-red-contrib-inspector nodes are invaluable along with your browser dev tools. Even just viewing the Node-red runtime log is easier using the inspector much easier than trying to use a terminal window. So well worth the effort of setting it up and working out how to use it.