Joshua's Cheatsheets-Node and NPM - Cheatsheet
Light
help

Upgrading NPM itself

  • Check version with npm -v
  • Upgrade depends on OS - full details

    • *nix - npm install -g npm@latest or npm install -g npm@next
    • Windows: Use this tool

      • npm install --global --production npm-windows-upgrade
      • npm-windows-upgrade --npm-version latest or npm-windows-upgrade
      • If any of the above fail:

        • Make sure you are in elevated prompt
        • Set-ExecutionPolicy Unrestricted -Scope CurrentUser -Force
        • If still failing, you might need to upgrade Node - [link]https://nodejs.org/en/download/)

Finding the NPM source directory

where npm

Upgrading Node itself version

  • On Unix

    • Recommended way 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

Init

npm init, or without the step-by-step questions, npm init -y

Some useful install flags - see full list here

Flag Full Flag Description
-g --global Save globally instead of into local project
NA --link Link globally installed packages into local project
-P --save-prod Save into dependencies section. Default
-D --save-dev Package will show in devDependencies
-O --save-optional Package will show in optionalDependencies
NA --no-save Does not save to package.json at all!

Semver

Updating release versions

You can use npm version {semVerString} to set a new version. This will update package.json, package-lock.json, and npm-shrinkwrap.json.

A helpful thing to remember is that, without any other arguments, this does a lot of automatic stuff you might not want:

  • Adds a new git commit with automated message

    • Use custom with -m arg
  • Add a new git tag, and points it at the auto commit

    • Use --no-git-tag-version to disable
  • Updates the npm files (as mentioned above)
  • Runs preversion, version and postversion under scripts.

Update version without tagging OR adding commit

Use the --no-git-tag-version arg, or git-tag-version=false

Auto version bump

If you don't want to manually type out a version string, you can just auto bump the version, so long as you are using a valid semVer in your npm files. Use npm version {incrementTarget} where {incrementTarget} is one of:

  • major
  • minor
  • patch
  • premajor
  • preminor
  • prepatch
  • prerelease

List installed packages

Courtesy of jkomyno

# NPM
npm list -g --depth 0

# Yarn
yarn global list

Find top level package that is using another

If we have package {top-level} that is using {sub-depend}, but we don't know that - all we know is that one of our top packages is using "sub-depend", we can find it out by using:

npm ls {sub-dependency-name}

List versions

node -v

and

npm -version

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"

Get the version of a package

NPM

npm list {packageName}

Yarn

yarn list --pattern {packageNameOrPattern} Or... yarn list | grep {packageNameOrPattern} Or... yarn global list --pattern {packageNameOrPattern}

Install a very specific version of a package (from CLI)

You can actually use similar syntax as a package.json file - e.g. tslint@^5.0.0.

Updating / upgrading packages used in package.json

  • NPM

    • You can use npm update to pull in updated packages (will also update your package-lock.json automatically)
    • You can use npm outdated to see what your package.json is requesting VS what is published

  • Yarn:

    • Upgrade to latest specified by package.json yarn upgrade
    • Same as above, but interactively: yarn upgrade-interactive
    • Upgrade to absolute latest, ignoring package.json (for example, major version bump): yarn upgrade-interactive --latest

Installing a specific version

  • NPM

    • npm install {packageName}@{semVerPattern}
  • Yarn

    • yarn add {packageName}@{semVerPattern}

How to deal with nested dependency versions

A somewhat common issue is trying to force a direct dependency (listed under dependencies or devDependencies in package.json) to use a specific version of their dependency.

This is not something you normally dictate - it is normally handled automatically by NPM or Yarn - but sometimes you might want to force a certain sub-dependency version due to a vulnerability; for example, if you use my-dependency which has a nested dependency of my-sub-depend locked at v0.0.1, which has a severe vulnerability patched in v0.0.2.

There are multiple ways to approach this, and the best starting spot is this S/O Question.

  • NPM

    • Use Shrinkwrap file

      1. Run npm shrinkwrap
      2. Manually edit the auto-created npm-shrinkwrap.json file

        • Use Semver targeting to force the new version you want
      3. Run npm install again
    • Use an automated solution, like npm-force-resolutions
  • Yarn

    • Yarn is way easier to use overrides with, in comparison to NPM
    • Just add resolutions object to package.json with overrides

When in doubt and fubar'd beyond repair...

npm cache clean -force

When REALLY f'ed up

rm -rf node_modules
npm cache clean -force
npm install

Installing peer-dependencies

Many packages, especially lint configs, will require other dependencies (peer dependencies) but will not install them automatically. You could manually figure out what's needed (for example, by running npm ls) and manually install one by one, or use a tool like install-peerdeps.

# See: https://www.npmjs.com/package/install-peerdeps#usage

# NPM
npm install -g install-peerdeps

# Yarn
yarn global add install-peerdeps

install-peerdeps --dev {package}
# OR
install-peerdeps {package}
# OR
install-peerdeps <package>[@<version>]

You can list peer dependencies needed by a package by using

# NPM
npm info "{package}" peerDependencies

Using global modules / packages

You can use globally installed packages by taking advantage of link - which basically just symlinks from the global folder to your project directory. Once something is installed globally, you can simply run npm link {packageName} in the directory where you want to be able to access it.

WARNING: This is not advised! In general, packages that good to install globally and use that way are CLI tools and devops / formatting packages, not actual dependencies that could impact a project.

Currently, Yarn will allow linking of dev packages (e.g. yarn link in dev, yarn link {name} in test), but tends to have issues with globally installed versions (GH issue).

NPM Install Errors

  • "No prebuilt binaries found"

    • There is probably a dependency that was built using C++ or other codebase and is missing a prebuilt binary for your versions of node/ABI#. Check your ABI and compare against what is hosted as a prebuilt binary.

      • If you can't find a prebuilt to use, just build yourself (check dependency's package.json and .gyp for details). You will probably also need build tools installed, depending on OS:

        npm install --global --production windows-build-tools
  • General build errors ("MSBuild", etc)

    • Make sure that you have build tools installed (see above)

      • Make sure built-tools install WORKED

        • Make sure to run as Admin
        • Check log, and if necessary, manually run the installer it downloads
    • You can switch what version of MSVC you are targeting:

      • For example: npm config set msvs_version 2015 -g
    • Try to get Node to use a prebuilt-binary instead of building from scratch

      • Check the log to see why node is trying to rebuild/build

        • For example, is a firewall blocking the binary download?
        • Some install scripts will let you specify a binary source URL
      • A quick hack if you can find a pre-built binary to download, is to save it to the corresponding npm-cache folder, and then run npm rebuild {packageName} before trying to re-install.

        • You should see Cached binary found at ... if this trick worked
        • Found via this comment

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
  • 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.

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();

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);
}

Detecting when a script / file is being run via CLI

There is a really hand 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)
}

Also, see "Accessing the main module"

Read in package.json within script file

const packageInfo = require('./package.json');
console.log('Version = ' + packageInfo.version);

List tasks

npm run

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';

Details

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');
});

Building a NPM Package - Tips

Resources

Misc tips

  • Know about "scoped packages"

    • You can publish names with slashes in them, like @joshua/right-pad
    • These by default are scoped to private
    • use npm publish --access=public to publish public
    • Thanks to this post for the tip
  • Don't forget about npm link for local testing

    • In package folder npm link
    • In other project you want to test the package with npm link {packageName}
  • If you are making both a CLI and a regular module, don't forget to have both fields:

    • main (points to index.js, or whatever your main module file is)
    • bin (points to the cli.js, or whatever your CLI file(s) are)

Exposing a CLI

You can specify that a specific file will receive CLI commands (instead of index.js), by using the bin field in your package.json.

If you want the command trigger (e.g. what someone types in their CLI to hit your package) to just be your package name, then bin can just be the path to the file:

{
	"bin": "cli.js"
}

But if you want to expose multiple commands, make bin an object with the commands as fields:

{
	"bin": {
		"myapp-foo": "cli-foo.js",
		"myapp-bar": "cli-bar.js"
	}
}

Make sure that all files exposed through bin start with #!/usr/bin/env node, otherwise they are not executed with the node executable!

Warning: If you are using npm link for local development, you might need to re-run npm link every time after modifying bin in order for the changes to be picked up globally. At least, I had to in my case.

See docs for details.

Publishing

You can use npm publish --dry-run to get a preview of what files would be published.

If you want to exclude files from being distributed as part of your package, you have a few options:

  • Use .gitignore file

    • Downside: this also prevents files from showing up on Github / tracked in Git
    • Not a good solution for preventing large files that you still want tracked in Git
  • Use .npmignore file

    • NOT RECOMMENDED, for several reasons:

      • Conflicts with .gitignore file - NPM will not even look at that file if .npmignore exists.
      • This is dangerous because if you exclude a password file in your .gitignore but forget to duplicate the rule to your .npmignore file, it will be included in your package!
  • Use package.json -> "files": []

    • This is the best option
    • You can move all the files you want to include into a separate folder, and then only include that folder. For example:

      {
      	"files": [
      		"src/"
      	]
      }

Express

Great cheatsheets

Quick Ref Table

Side Thing Description Relev type sig
Request req.params Captures named params, made explicit in route signature. {[index: string]: string | undefined}
Request req.query Captures QueryString key-pair vals. {[index: string]: string | undefined}
Request req.body Captures body payload key-pairs

Requires body parsing middleware
{[index: string]: any}

How do I...

  • Capture variable through route?

    • Use :var syntax in route, then access through injected req.params

      app.get('/users/:userId', (req, res) => {
      	const userId = req.params.userId;
      });
    • Or through actual request payload, with req.body (if using body-parser)
    • Or through query params with req.query.{queryParam}
  • Inject a variable into the route?

    • You probably want to use middleware
    • Actually very easy to write your own with just a few lines of code...
  • Capture a parameter and check if it was used in the request?

    • GET (QueryString)

      • Use req.query, which returns object of key/pair values corresponding to querystring.
      • Check for valid value through standard JS logic; e.g., if you are expecting a string for /?name={name}, maybe do !!req.query.name
    • POST (Body)

      • Basically same as GET, but use req.body, to access POST body params (assuming you switch to body with POST)
      • Requires body-parsing middleware
    • Named param in route

      • If you are capturing parameters explicitly as part of the route, you can use req.params to get the captured value
      • Example, if route is /book/:id, you can get ID through req.params.id
    • ALL

      • If you want to accept the same parameter, and allow it to be passed via any of the three methods (QueryString, Body, or Named Params), that is a little trickier
      • There used to be an API method for this, req.param({name}, {defaultValue}), but it was deprecated.
      • If you really need this, you could easily write your own wrapper method to check all three inputs and validate, and/or take a look at the original code, here.
      • Relevant S/O thread

Things to note

Param types

The built-in query string parser in Express does not infer types. For example, you might be tempted to think that if your request looks like /?name=joe&age=13, then req.query.name would be typeof string, and req.query.age would be typeof number. This is wrong. They are all inferred as strings.

In addition, since req.query is an object, if you try to query a param that was not provided, you will get back undefined, not an empty string.

All of the above is also true for req.params, but not for req.body, since if JSON is passed via request, then the types are preserved without any special parsing needed

Common Issues

  • ERR_HTTP_HEADERS_SENT

    • This indicates that you are basically trying to send a response back multiple times, or trying to set headers on a response after it has already been sent
    • A very common reason for this is forgetting to return after sending a response, so your code continues and tries to send a second response.

  • BadRequestError: request aborted

    • This is kind of a generic error, indicating that something interrupted the process of reading in the request
    • Is likely to happen with the body-parser plugin
    • I personally hit this with mismatched Content-Length headers - where this error will throw if I send a request with an explicit Content-Length header that doesn't match the body size

      • Easy to accidentally do this if you are cloning Postman requests

Debugging, profiling, etc.

Debugging

Call node --inspect-brk {nodeScriptFilePath} {args}

Make sure you either have auto-attach on in your VSCode settings, or start a debug session before running.

Profiling

For a plug-n-play solution, check out 0x.

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 of SETX)

    • Example: SETX API_KEY "123"&& node -p process.env.API_KEY ---> results in 123 printed to console
  • Windows PowerShell: $env:{KEY}="{VAL}"

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
  • Run npm install dotenv - to add it as a dependency
  • Add require('dotenv').config() as early as possible in your code, which will cause dotenv to map the contents of the file to process.env

    • After this point, process.env.MY_KEY will contain the value defined in .env if you have the pair MY_KEY={something} in the file

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 a example.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!

Markdown Source Last Updated:
Thu Mar 19 2020 20:52:36 GMT+0000 (Coordinated Universal Time)
Markdown Source Created:
Mon Aug 19 2019 17:06:24 GMT+0000 (Coordinated Universal Time)
© 2020 Joshua Tzucker, Built with Gatsby