Resources
What & Link | Type |
---|---|
nodejs.org | Official website |
--> Docs | Official Docs |
node.green | Compatibility table for NodeJS versions & JS versions |
Upgrading Node itself version
- On Unix, macOS
- My favorite option is managing node versions through asdf, since it can be used for managing other runtimes as well
- Another option is to use "N"
# Just needed once npm install -g n # Now upgrade sudo n stable
- On windows
- You can download new installer (MSI) and just install over current
- You can now use "NVM" (node version manager) for both Unix and Windows
NVM
- If you want to persist a version of Node, across terminal sessions, you need to set it as the default for NVM
- E.g.
nvm alias default 14.15.4
- E.g.
Listing Versions
node -v
Note: Node also has "ABI" version number. regular version number is like 10.##.# (LTS or non-lts). ABI version is regular integer
node -p "process.versions.modules"
Or to get all version info:
node -p "process.versions"
Copying to the Clipboard
On Windows, here is a one-liner you can use:
require('child_process').spawn('clip').stdin.end(formattedHtml);
In general, you are probably better off using the cross-OS package clipboardy.
Paths, paths, paths
Current directory
Often in Node scripts, you will need to reference something either by absolute or relative path, which might require knowing the full path of where the script is running.
__dirname
is a magic global that holds the string path of where the script resides - regardless from where it got called- Does not have trailing slash
- ESM equivalent (S/O):
import { dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url));
process.cwd()
returns the absolute path from where you invoked the script process - e.g. where you ran your command from
Normalizing paths (POSIX vs non-POSIX, aka Windows)
There is a really great native library for Node called path
which has all kinds of methods for cleaning up and parsing paths. For example, if I want to create a path based on current directory, and then normalize it because I'm not sure which OS it is going to be running on, I might use something like this:
const path = require('path');
const myFilePath = path.normalize(__dirname + '../subdir/myFile.js');
However, that only normalizes it for the OS you are on. If you want to force a standard, across any env, that takes more work. Quick hackish example:
currFilePath = currFilePath.replace(/[\/\\]{1,2}/gm,'/');
Read more about path, here
Get list of node flags
node --help
Or here.
Using the Node CLI / REPL
You can use the -e
argument to evaluate (eval) raw code string, or -p
to both evaluate and "print" the output.
node -p "Math.min(24,2);"
- Results in "2" being printed to console.
You can use the -i
or --interactive
flag to force node
into REPL mode, even if stdin is not a terminal.
- This is important to note if you are trying to do something like recursively call
node
from withinnode
viachild_process
or something like that (without the flag, it won't flush stdout until stdin is explicitly closed via.end()
).
Running a CLI command / system / bash / shell commands from within a node script
Recommended way is with child_process.exec
.
const childProc = require('child_process');
var lsResults = childProc.execSync('ls');
// Note - exec returns buffer, so need to convert
var lsResultsString = lsResults.toString();
Calling Node From Node Via Child_Process
This is a pretty specific scenario, but if you end up trying to call node
from within node
, via exec
or spawn
:
- You might want to call it with
--interactive
to force interactive mode (or callstdin.end()
to flush at the end) - You will need to send
EOL
characters after sending input to the CLI - You might want to read this comment summarizing some approaches
Changing directory
If you want to use cd
, you need to use it with the command you want to run at the same time - e.g. a single line input to child_process.exec
. Otherwise, the change of directory will not persist between commands. For example:
const childProc = require('child_process');
// Right:
childProc.execSync('cd foo && ls');
// Wrong:
chldProc.execSync('cd foo');
childProc.execSync('ls');
A recommended alternative is to pass the directory you want to execute the command in through the cwd (working directory) option:
const childProc = require('child_process');
childProc.execSync('ls', {
cwd: 'foo'
});
Receiving CLI arguments
Access through process.argv
. It should follow the following syntax:
if (Array.isArray(process.argv)){
// 0 = path to nodejs - {string}
let nodePath = process.argv[0];
// 1 = path to current executing file - {string}
let currFile = process.argv[1];
// 2, 3, etc. = arguments
let argsArr = process.argv.slice(2);
}
CLI Argument Parsing
It's generally best to defer to a argument parsing library instead of rolling your own parsing code.
I'm partial to libraries that support strong static-typing and type-inference, as that solves a lot of headaches around building a CLI app. Some example libraries that fit into this category are privatenumber/type-flag and Schniz/cmd-ts.
Starting with Node 18.3, there is now a built-in tool for arg parsing -
parseArgs
fromnode:util
. It is not as full-featured as a dedicated CLI library likecmd-ts
, but is good enough to work for quite a few more simple use-cases.
For more details, see my other cheatsheet section - CLI Framework Libraries
Detecting when a script / file is being run via CLI
There is a really handy trick for, in your code, to detect if it is being run directly versus imported by other code. You can use:
if (require.main === module) {
// This code will only execute if the file is called *directly* (e.g. via CLI)
}
Or, for ESM code
import esMain from 'es-main';
if (esMain(import.meta)) {
// This code will only execute if the file is called *directly* (e.g. via CLI)
}
Also, see "Accessing the main module"
Read in package.json within script file
const packageInfo = require('./package.json');
console.log('Version = ' + packageInfo.version);
Also see "JS Modules - How to Import JSON?"
For most robust approach, stick to filesystem methods.
fs-extra
even has areadJson
method.
Setting globals
There are not many good reasons to do this, but if for some reason you need to set a true global (not as in file global or top of closure global, but as in pollutes every file once imported), here is how:
global.findMyAnywhere = 'Hello';
// Or...
globalThis.findMyAnywhere = 'Hello';
If you want TypeScript to understand the type of these globals across your codebase, you can follow the steps outlined here
Console EOF / process.stdin.on('end')
On Unix, pressing CTRL+D usually results in the system returning an EOF (end-of-file) to whatever is listening to the terminal. In node, you often see this listened to as:
process.stdin.on('end', ()=>{
// Do something
}
However, this flat out does not work on Windows. Futhermore, pressing CTRL+C, since it sends the exit command, does not give that listener a chance to execute. The workaround is to use the process signal event SIGINT
listener:
// Redirect windows CTRL+C to stdin-end
process.on('SIGINT',function(){
// Do whatever you want here - finish up stuff, etc.
// ...
// Emit EOF / end event
// https://nodejs.org/api/stream.html#stream_event_end
process.stdin.emit('end');
});
Debugging, profiling, etc.
This is a great answer on S/O that lists a bunch of commonly used tools for debugging, profiling, and more.
Debugging
Call node --inspect-brk {nodeScriptFilePath} {args}
node --inspect
is the same as above, but without breaking first
If you can't pass flags directly to node, you can also set them via a
NODE_OPTIONS
environment variable - e.g.,NODE_OPTIONS="--inspect-brk" {MY_CMD}
Make sure you either have auto-attach on in your IDE settings, or start a debug session before running.
If you are using VSCode, you can also manually attach to running NodeJS processes, by using
Command Palette -> Attach to Node Process
Profiling
For a plug-n-play solution for analyzing performance, check out 0x, or flamebearer.
Clinic.js is a nifty suite of profiling tools,, all wrapped into one easy to install and use package.
Throwing errors
The recommended way to throw errors in Node is with the explicit error constructor.
throw new Error('Computer says "no"...');
Good read: Flavio Copes - Node Exceptions
Environment Variables
Reading values
From your CLI, the fastest way to view your environment variables is node -p process.env
.
From within code that is running with NodeJS, you can easily access any environment variable by picking it off the process
global variable object. It is super common to use this as a way to avoid putting credentials in code, like so:
const myApiClient = new ApiClient({
id: process.env.API_CLIENT_ID,
superSecretPass: process.env.API_CLIENT_PASS
});
Setting values
There are multiple ways to set env values, with varying levels of setup required.
From the CLI
Since process.env
is basically just a map of your OS's environment variables, setting values for it depends on your OS and even what CLI you use:
- BASH, or "bash-like" CLIs:
{KEY}={VAL}
- Windows CMD:
SETX {KEY} "{VAL}"
(or, temporary,SET
instead ofSETX
)- Example:
SETX API_KEY "123"&& node -p process.env.API_KEY
---> results in123
printed to console - Example
SET port=3001 && node server.js
- Example:
- Windows PowerShell:
$env:{KEY}="{VAL}"
- NodeJS REPL:
node -e "process.env.{KEY} = {VAL}"
For all of the above options, be careful about quotes / escaping.
From a .env
File
Since it can get tedious setting and checking variables from the command line, most devs prefer to keep these values stored in a file, and have Node read the values out when executing. This also has the added benefit of keeping those values out of your OS variables.
However, unlike how it reads OS variables, mapping values from a file to process.env is not baked into Node, so you will need to use a dependency to add that ability. The most popular is probably dotenv
.
You can read the docs for how to use it, but its pretty simple:
- Add a
.env
file to the root of your project, with key pair values- These shoule be written as
KEY=VAL
- These shoule be written as
- Run
npm install dotenv
- to add it as a dependency - Add
require('dotenv').config()
as early as possible in your code, which will causedotenv
to map the contents of the file toprocess.env
- After this point,
process.env.MY_KEY
will contain the value defined in.env
if you have the pairMY_KEY={something}
in the file
- After this point,
WARNING: Be careful about sharing your
.env
file. If it contains "secrets" (API keys, credentials, etc.) you probably want to add it to your.gitignore
, and create aexample.env
which contains the same keys as.env
, but with empty values for a dev to fill in with their own credentials.
From within Code
From within your code that is running on Node, you can override existing values, or set new ones, simply by treating process.env
as a regular object. For example:
process.env.API_KEY = 'ABC123';
Note: this only sets the value for Node's process and any child processes; this doesn't actually change your OS environment variable value after the process exits! See responses and linked answers to this StackOverflow for details.
Node.js - Parallelization Options / Concurrency Options
This is a really good rundown of the state of parallelization techniques in Node.js and some useful libraries - https://snyk.io/blog/node-js-multithreading-worker-threads-pros-cons/
In Node.js, you have three primary built-in options for parallelization:
- Clusters (multiprocessing)
- True multi-processing
- Manual use of child_process
- Worker Threads (multi-threading)
You also have the option of just wrapping your entire app in a multi-process spawner / manager, like PM2, if it is already amenable to being run in multiple parallel instances (example results).
Node.js - Parallel Processing Libraries / Abstractions
Library | Worker Threads | Cluster / Multiprocess | Includes Type Definitions |
---|---|---|---|
poolifier | ✅ | ✅ | ✅ |
workerpool | ✅ | ✅ 🍴 | ✅ |
tinypool | ✅ | ✅ | ✅ |
piscina | ✅ | ❌ | ✅ |
synckit | ✅ | ❌ | ✅ |
throng | ❌ | ✅ | ❌ |
threads.js | ✅ | ❌ | ✅ |
🍴 = Using
child_process
instead ofcluster
Cross-Platform Development
Detecting Platform
You can use os.platform()
or process.platform
to get the platform
string (e.g., win32
, darwin
, etc.). These are functionally equivalent.