mod_03 - NPM Essentials¶
npm is the package manager for Node , and it's simultaneously the best and worst thing about the ecosystem
It gives you access to 2+ million packages for everything from web frameworks to "is-odd" (yes, that's a real package with millions of weekly downloads). It also means every project you build depends on hundreds of transitive dependencies written by strangers on the internet. Understanding npm - how it resolves , installs , and validates packages - is non-negotiable for Node security
installing packages¶
npm install¶
Adds a package to dependencies in package.json
# Install a package and save to dependencies
npm install express
# Install a specific version
npm install express@4.18.2
# Install from GitHub
npm install github:expressjs/express
# Install a tarball
npm install ./local-package.tgz
npm install --save-dev¶
Adds a package to devDependencies - things you need for development but not in production
npm install --save-dev jest
npm install -D eslint # -D is shorthand for --save-dev
Dev dependencies are NOT installed when NODE_ENV=production or when npm install --production is used. This matters for deployment and CI - don't put runtime dependencies in devDependencies
npm install -g¶
Install a package globally - available as a CLI command system-wide
npm install -g nodemon
npm install -g eslint
Avoid global installs in production. They pollute the system-wide Node installation and make version management a nightmare. Use npx for one-off commands or add to devDependencies and use npm scripts. Global packages are fine for developer tooling on your local machine (nodemon, http-server, typescript)
# Instead of global install:
npm install -g create-react-app
create-react-app my-app
# Use npx:
npx create-react-app my-app
npm ci vs npm install¶
npm ci is for CI/CD environments - deterministic installs from the lockfile
# Development install - may update lockfile
npm install
# CI install - fails if package.json and lockfile don't match
npm ci
Differences:
npm cideletesnode_modulesbefore installing - clean state every timenpm cinever modifiespackage.jsonorpackage-lock.json- it uses the lockfile exactlynpm ciis faster - it skips dependency resolution and goes straight to installationnpm cifails ifpackage-lock.jsonis outdated relative topackage.json
Use npm ci in CI pipelines. npm install in CI can silently update your lockfile and introduce untested dependency changes
package.json and package-lock.json¶
package.json¶
Describes your project - name , version , dependencies , scripts , metadata
{
"name": "my-app",
"version": "1.0.0",
"dependencies": {
"express": "^4.18.0"
},
"devDependencies": {
"jest": "^29.0.0"
}
}
This is your manifest. Commit it to version control. The version ranges in dependencies tell npm what versions are acceptable
package-lock.json¶
Locks every dependency to an exact version - including all transitive dependencies
{
"name": "my-app",
"lockfileVersion": 3,
"packages": {
"node_modules/express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-...base64-hash...",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1"
}
}
}
}
Always commit package-lock.json to version control. It ensures everyone on the team (and your CI/CD pipeline) gets the exact same dependency tree. If you use .npmrc with package-lock=false , you're asking for production-only bugs that only appear because someone's resolver picked a different minor version
semantic versioning¶
npm uses semver (semantic versioning): MAJOR.MINOR.PATCH
flowchart LR
V("version 4.18.2") --> MAJ["MAJOR - breaking changes"]
V --> MIN["MINOR - new features (backward compatible)"]
V --> PAT["PATCH - bug fixes (backward compatible)"] Version ranges in package.json:
| Range | Meaning | Example match |
|---|---|---|
1.2.3 | Exact version | Only 1.2.3 |
^1.2.3 | Same major (minor+patch) | 1.2.3 to <2.0.0 |
~1.2.3 | Same minor (patch only) | 1.2.3 to <1.3.0 |
>=1.2.3 | Minimum version | 1.2.3+ |
* | Any version | Everything |
latest | Latest published | Whatever is current |
# See what versions are available
npm view express versions
# See what a range resolves to
npm view express@^4.0.0 version
# Check outdated packages
npm outdated
Security rule: Use ^ ranges for most dependencies (you get bug fixes automatically) but pin exact versions for security-critical packages or when you need reproducible zero-day response. For CI , npm ci ignores ranges and uses the lockfile anyway
npm audit and security¶
npm audit¶
Scans your dependency tree for known vulnerabilities
# Regular audit
npm audit
# Fix vulnerabilities automatically (may update lockfile and package.json)
npm audit fix
# Audit for production dependencies only
npm audit --production
npm audit compares your dependencies against the npm Advisory database - CVEs reported to npm. It categorizes vulnerabilities by severity: critical , high , moderate , low
# Example output
# === npm audit security report ===
#
# moderate Prototype Pollution in lodash
# Package: lodash
# Dependency of: express
# Path: express > lodash
# More info: https://github.com/advisories/GA-xxxx
#
# fix available via `npm audit fix`
What npm audit can't do: find vulnerabilities in your own code , detect malicious packages that aren't reported to npm , or check runtime behavior
npm doctor¶
Runs diagnostics on your npm environment
npm doctor
# Checks:
# - npm version is current
# - Node.js version is compatible
# - Registry connectivity
# - Permissions on node_modules
# - Git access
npm verify¶
Checks the integrity of installed packages against the hashes in your lockfile
# Verify integrity of all installed packages
npm cache verify
# Recalculate integrity hashes
npm rebuild
npm signature verification¶
Node 20+ includes experimental support for verifying package signatures
# Configure npm to require signatures
npm config set sign-git-tag true
# Audit signatures (Node 20+)
npm audit signatures
package resolution and hoisting¶
npm installs packages in node_modules with a nested tree structure
flowchart TD
root["my-app/"] --> nm["node_modules/"]
nm --> exp["express/<br/>direct dependency"]
exp --> nm2["node_modules/"]
nm2 --> acc["accepts/<br/>express's dependency (nested)"]
nm --> lod["lodash/<br/>hoisted to top level"] Modern npm (v7+) uses the node_modules hoisting algorithm to minimize nesting. If multiple packages depend on different versions of the same package , npm nests the incompatible versions
# See the actual resolved tree
npm ls
# See why a specific version is installed
npm explain express
summary¶
npm installadds todependencies;--save-devadds todevDependencies- Use
npm ciin CI/CD for deterministic installs from lockfile - Always commit
package-lock.jsonto version control - Semver ranges:
^for minor+patch updates ,~for patch-only - Run
npm auditregularly and fix vulnerabilities - Avoid global installs - use
npxinstead npm lsshows the resolved dependency tree- Every dependency is a supply chain risk - audit before you install
prerequisites¶
mod_02_es_modules.md - ES module syntax
next -> mod_04_package_json.md