All topics
General · Learning hub

npm notes for developers

Master npm with a curated set of 8 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore General notes
npm

npm Fundamentals

npm Fundamentals npm (Node Package Manager) is the default package manager for Node.js and the world's largest software registry. Understanding it deeply - inst

npm Fundamentals

npm (Node Package Manager) is the default package manager for Node.js and the world's largest software registry. Understanding it deeply - install flags, lock files, scripts, and the audit tools - is essential for any JavaScript developer.

Initializing & Installing

# Initialize a new project
npm init                        # Interactive prompt
npm init -y                     # Accept all defaults (fastest)
npm init --scope=@myorg         # Scoped package init

# Install packages
npm install express             # Install + add to dependencies
npm install -D typescript       # Install + add to devDependencies
npm install -O nodemon          # Optional dependency
npm install -g vercel           # Global install (available as CLI)
npm install                     # Install all from package.json
npm install express@4.18.2      # Exact version
npm install express@latest      # Latest (same as no tag)
npm install express@next        # Next/beta dist-tag
npm install github:expressjs/express        # From GitHub repo
npm install github:expressjs/express#v4.18  # Specific tag/branch
npm install ./path/to/local-package         # Local package
npm install https://example.com/pkg.tgz     # Remote tarball

# Install flags
npm install --save-exact        # Pin to exact version (no ^ or ~)
npm install --legacy-peer-deps  # Skip peer dep resolution errors
npm install --force             # Force re-install even if present
npm install --prefer-offline    # Use cache, avoid network

# npm ci (clean install - strictly from lock file)
npm ci                          # Faster than npm install in CI
# npm ci vs npm install:
# - npm ci requires package-lock.json to exist
# - npm ci deletes node_modules first, then installs exact versions
# - npm ci never updates package-lock.json
# - npm ci fails if lock file doesn't match package.json
# Use npm ci in CI/CD pipelines; npm install in local development

Updating & Removing

# Update packages
npm update                      # Update all to latest allowed by semver range
npm update express              # Update specific package
npm update -g npm               # Update npm itself
npm update --save               # Update + write new versions to package.json

# Check for outdated packages
npm outdated                    # Show current/wanted/latest versions
npm outdated -g                 # Check global packages

# Uninstall
npm uninstall express           # Remove from node_modules + package.json
npm uninstall -D typescript     # Remove devDependency
npm uninstall -g vercel         # Uninstall global

# Prune (remove packages not in package.json)
npm prune                       # Remove extraneous packages
npm prune --production          # Remove devDependencies (for production builds)

Listing & Auditing

# List installed packages
npm list                        # Full tree (verbose)
npm list --depth=0              # Top-level only
npm list -g --depth=0           # Global top-level
npm list express                # Find a specific package in tree
npm ls express                  # Alias for npm list

# Package info
npm info express                # Full registry info
npm info express version        # Latest version only
npm info express versions       # All published versions
npm info express peerDependencies
npm docs express                # Open package docs in browser
npm home express                # Open package homepage
npm repo express                # Open GitHub repo

# Security audit
npm audit                       # Show vulnerabilities
npm audit --json                # JSON output for parsing
npm audit fix                   # Auto-fix vulnerabilities (semver-compatible)
npm audit fix --force           # Force updates (may introduce breaking changes)
npm audit fix --dry-run         # Preview what would change

# Cache management
npm cache clean --force         # Clear npm cache
npm cache verify                # Verify cache integrity

Running Scripts

# Run scripts defined in package.json "scripts"
npm run dev
npm run build
npm run test
npm test          # Shorthand (no "run" needed for test)
npm start         # Shorthand for npm run start
npm stop          # Shorthand for npm run stop

# Pass args to scripts (after --)
npm run test -- --watchAll
npm run build -- --profile
npm test -- --testPathPattern=auth

# List all available scripts
npm run

# npx: run a package binary without installing globally
npx create-next-app@latest my-app
npx tsc --noEmit
npx prisma migrate dev
npx tsx scripts/seed.ts

# npx with specific version
npx cowsay@1.5 "hello"

# Check npm version and config
npm --version
npm config list
npm config list --json
npm config get registry         # https://registry.npmjs.org/
npm config set registry https://registry.npmjs.org/
npm config set save-exact true  # Always pin exact versions globally
npm

package.json Deep Dive

package.json Deep Dive package.json is the heart of every Node.js project. Beyond name and version, it contains exports, engines, scripts lifecycle hooks, works

package.json Deep Dive

package.json is the heart of every Node.js project. Beyond name and version, it contains exports, engines, scripts lifecycle hooks, workspace configuration, and dependency overrides. Understanding all fields helps you write professional packages and avoid gotchas.

Key Fields Reference

{
  "name": "@myorg/my-package",   // Scoped or unscoped; lowercase, no spaces
  "version": "1.4.2",            // semver: major.minor.patch
  "description": "A short description",
  "license": "MIT",
  "author": "Jane Doe <jane@example.com> (https://jane.dev)",
  "homepage": "https://github.com/myorg/my-package#readme",
  "repository": {
    "type": "git",
    "url": "https://github.com/myorg/my-package.git"
  },
  "bugs": { "url": "https://github.com/myorg/my-package/issues" },
  "keywords": ["node", "utils", "parser"],

  // Entry points
  "main": "./dist/index.js",      // CommonJS entry (legacy)
  "module": "./dist/index.mjs",   // ESM entry (bundlers)
  "types": "./dist/index.d.ts",   // TypeScript declarations

  // Modern exports map (Node 12+, overrides main)
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "types": "./dist/index.d.ts"
    },
    "./utils": {
      "import": "./dist/utils.mjs",
      "types": "./dist/utils.d.ts"
    }
  },

  // Files to include when publishing (whitelist)
  "files": ["dist", "src", "!src/**/*.test.*"],

  // CLI binaries
  "bin": {
    "my-cli": "./bin/cli.js"
  },

  // Node/npm version requirements
  "engines": {
    "node": ">=18.0.0",
    "npm": ">=8.0.0"
  },

  // Mark as browser/Node only package
  "browser": true,
  // OR: ""browser": false"  (Node-only)

  // ESM vs CJS
  "type": "module"  // .js files treated as ESM; use .cjs for CommonJS
  // "type": "commonjs"  // Default; .js treated as CJS; use .mjs for ESM
}

Dependencies

{
  // Runtime dependencies (shipped to production/consumers)
  "dependencies": {
    "express": "^4.18.2",
    "zod": "^3.22.0"
  },

  // Development only (not installed by consumers of your package)
  "devDependencies": {
    "typescript": "^5.3.0",
    "jest": "^29.7.0",
    "@types/node": "^20.0.0",
    "eslint": "^8.57.0"
  },

  // Must be installed by the consumer (not installed automatically)
  // Use for: plugins that expect a specific version of a host package
  "peerDependencies": {
    "react": ">=17.0.0 || ^18.0.0",
    "react-dom": ">=17.0.0 || ^18.0.0"
  },
  // Mark peer deps as optional (no warning if missing)
  "peerDependenciesMeta": {
    "react-dom": { "optional": true }
  },

  // Optional: if install fails, npm continues (platform-specific packages)
  "optionalDependencies": {
    "fsevents": "^2.3.3"  // macOS file watching
  },

  // Override nested dependency versions (resolve security issues)
  "overrides": {
    "semver": "^7.5.4",       // Override ALL semver versions in tree
    "express": {
      "qs": "^6.11.2"         // Override qs only when required by express
    }
  }
}

Scripts Lifecycle

{
  "scripts": {
    // Lifecycle hooks run automatically
    "preinstall":   "node check-node-version.js",
    "postinstall":  "patch-package",         // Run after npm install
    "prepare":      "husky",                 // Runs on npm install + npm publish
    "prepublishOnly": "npm test && npm run build",  // Only before publish
    "prepack":      "npm run build",         // Before packing a tarball
    "postpack":     "",

    // User-defined scripts
    "dev":          "next dev --turbopack",
    "build":        "next build",
    "start":        "next start",
    "test":         "jest --passWithNoTests",
    "test:watch":   "jest --watch",
    "test:coverage": "jest --coverage",
    "lint":         "eslint src --ext .ts,.tsx",
    "lint:fix":     "eslint src --ext .ts,.tsx --fix",
    "type-check":   "tsc --noEmit",
    "format":       "prettier --write \"src/**/*.{ts,tsx,json}\"",
    "db:generate":  "drizzle-kit generate",
    "db:migrate":   "drizzle-kit migrate",

    // Compound scripts (parallel with & or concurrently)
    "dev:all":      "concurrently \"npm:dev\" \"npm:db:studio\"",

    // Pre/post hooks for custom scripts too
    "prebuild":     "npm run type-check",
    "postbuild":    "echo Build complete!"
  }
}

Workspaces

// Monorepo setup: root package.json
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "packages/*",      // All packages in packages/ directory
    "apps/*"           // All apps in apps/ directory
  ],
  "scripts": {
    "build": "npm run build --workspaces",
    "test":  "npm run test --workspaces --if-present"
  }
}

// Install dependency in specific workspace
npm install react --workspace=apps/web
npm install lodash -w packages/utils   // -w = --workspace shorthand

// Run script in specific workspace
npm run build --workspace=apps/web
npm run test -w packages/utils

// Run script in all workspaces
npm run build --workspaces
npm run test --workspaces --if-present  // --if-present skips if script missing

// Install all workspaces from root
npm install  // Hoists shared deps, symlinks workspace packages

// Cross-workspace dependencies (no version needed - uses local)
// packages/web/package.json:
{
  "dependencies": {
    "@myorg/utils": "*"  // Resolves to packages/utils symlink
  }
}
npm

Versioning & Publishing

npm Versioning & Publishing Semantic versioning, version ranges, and the publish workflow are essential knowledge for anyone maintaining a library or CLI tool.

npm Versioning & Publishing

Semantic versioning, version ranges, and the publish workflow are essential knowledge for anyone maintaining a library or CLI tool. Understanding them also helps you manage dependencies responsibly in applications.

Semantic Versioning (semver)

# semver format: MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
# 1.4.2-beta.1+build.123

# MAJOR: breaking (incompatible API changes)
# MINOR: new features (backward compatible)
# PATCH: bug fixes (backward compatible)

# Version ranges in package.json:
# Exact:    "1.4.2"    - only this version
# Caret:    "^1.4.2"  - >=1.4.2 <2.0.0 (same major; allows minor+patch)
# Tilde:    "~1.4.2"  - >=1.4.2 <1.5.0 (same minor; allows patch only)
# Range:    ">=1.4.0 <2.0.0"
# Latest:   "*" or ""  - any version (dangerous)
# Any 1.x:  "1.x" or "1.*"

# Special cases:
# "^0.4.2"  → >=0.4.2 <0.5.0   (0.x.x: ^ only allows patch changes!)
# "^0.0.3"  → >=0.0.3 <0.0.4   (0.0.x: ^ pins exactly)
# Reason: before 1.0, minor versions may have breaking changes

# Prerelease versions (not matched by ^ unless explicitly specified)
# alpha < beta < rc < stable
# 1.0.0-alpha.1 < 1.0.0-alpha.2 < 1.0.0-beta.1 < 1.0.0-rc.1 < 1.0.0

# Check what a range resolves to
npx semver -r "^1.4.2" 1.4.3 1.5.0 2.0.0   # Tests versions against range

# Lock file pins exact versions regardless of ranges
# package.json: "^4.18.2" → package-lock.json: "4.18.3" (exact)

Bumping Versions

# npm version: bumps package.json, creates git commit + tag automatically
npm version patch        # 1.4.2 → 1.4.3  (bug fix)
npm version minor        # 1.4.2 → 1.5.0  (new feature)
npm version major        # 1.4.2 → 2.0.0  (breaking change)

# Prerelease increments
npm version prerelease --preid=alpha   # 1.4.2 → 1.4.3-alpha.0
npm version prerelease                 # 1.4.3-alpha.0 → 1.4.3-alpha.1
npm version prepatch                   # 1.4.2 → 1.4.3-0
npm version preminor                   # 1.4.2 → 1.5.0-0
npm version premajor                   # 1.4.2 → 2.0.0-0

# Set specific version
npm version 2.0.0-rc.1

# Skip git tag
npm version patch --no-git-tag-version

# Custom commit message
npm version patch -m "chore: release v%s"

# After version bump, push commit AND tag:
git push && git push --tags

Publishing to npm Registry

# Login to npm
npm login                        # Interactive
npm login --scope=@myorg         # For scoped packages
npm whoami                       # Check who you're logged in as
npm token list                   # List auth tokens
npm token create --read-only     # Create CI token

# What gets published?
# Whitelist: "files" field in package.json
# Blacklist: .npmignore (like .gitignore for publishing)
# Default: everything EXCEPT node_modules, .git, tests

# .npmignore example:
# src/
# tests/
# *.test.ts
# tsconfig.json
# .env*

# Preview what would be published (dry run)
npm pack --dry-run               # Lists files that would be packed
npm pack                         # Creates .tgz; inspect it
npm publish --dry-run            # Full dry run of publish

# Publish
npm publish                      # Publish public package
npm publish --access public      # Required for scoped packages on first publish
npm publish --access restricted  # Scoped package, private (requires paid account)
npm publish --tag beta           # Publish as beta dist-tag (not "latest")

# Publish prerelease without updating "latest" tag
npm publish --tag next           # Users install with npm install pkg@next

# Deprecate a version
npm deprecate my-pkg@1.0.0 "Use 2.x instead"
npm deprecate my-pkg@"<2.0.0" "All 1.x versions are deprecated"

# Unpublish (72-hour window, or request support)
npm unpublish my-pkg@1.0.0
npm unpublish my-pkg --force     # Unpublish entire package (careful!)

# dist-tags
npm dist-tag ls my-pkg           # List all dist-tags
npm dist-tag add my-pkg@2.0.0 latest
npm dist-tag add my-pkg@1.0.0 legacy
npm dist-tag rm my-pkg beta

Scoped Packages & Private Registry

# Scoped packages: @scope/package-name
# Scope can be your username (@alice/utils) or org (@myorg/sdk)
# Free scoped packages are public; private requires paid npm org account

# Install scoped package
npm install @myorg/design-system
npm install @myorg/design-system@2.1.0

# Publish scoped package
npm publish --access public       # First publish must set access explicitly
npm publish                       # Subsequent publishes remember access setting

# Use GitHub Package Registry instead of npmjs.com
# .npmrc in project root:
@myorg:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}

# Use Verdaccio for self-hosted npm registry
npm install -g verdaccio
verdaccio                         # Start server on :4873
npm config set registry http://localhost:4873
npm adduser --registry http://localhost:4873

# .npmrc for team (commit to repo, never commit tokens)
# registry=https://registry.npmjs.org/
# @myorg:registry=https://npm.pkg.github.com
# save-exact=true
# engine-strict=true
npm

npm Interview Questions

npm Interview Questions These questions come up in JavaScript, Node.js, and fullstack interviews. Deep knowledge of npm, dependencies, and the package ecosystem

npm Interview Questions

These questions come up in JavaScript, Node.js, and fullstack interviews. Deep knowledge of npm, dependencies, and the package ecosystem signals strong engineering judgment.

1. What is the difference between npm, yarn, and pnpm?

All three are JavaScript package managers that install packages from the npm registry. npm (Node Package Manager) is the default bundled with Node.js - widely compatible but historically slower. yarn (by Meta, 2016) was created to address npm's speed and reliability issues with deterministic installs (yarn.lock), parallel downloads, and offline caching. yarn v2/berry introduced Plug'n'Play (no node_modules, packages stored in a zip cache). pnpm is the newest and most efficient: it uses a content-addressable store where each version of a package is stored once globally, then hard-linked into node_modules. This means 100x faster installs if packages are cached, uses far less disk space, and by default creates a strict node_modules structure (only declared dependencies are accessible).

2. What is the purpose of package-lock.json and when should you commit it?

package-lock.json records the exact version, resolved URL, and integrity hash of every installed package (including transitive dependencies). While package.json specifies ranges (e.g., "^4.18.2"), the lock file pins the exact version that was installed (e.g., 4.18.3). This ensures every developer and CI environment installs identical dependencies, eliminating "works on my machine" issues caused by different package versions. Always commit package-lock.json for applications. For published libraries, many developers add it to .gitignore to avoid forcing consumers to use your exact dependency tree, though there are valid arguments for committing it to ensure test reproducibility. Never modify it manually - let npm manage it.

3. What is the difference between dependencies and devDependencies?

dependencies are packages required at runtime in production. When someone installs your library, they also get your dependencies installed. devDependencies are only needed during development and testing: compilers (TypeScript, Babel), test frameworks (Jest, Vitest), linters (ESLint), and build tools (webpack, Vite). They are NOT installed when users install your package (npm install --production), and not installed in npm ci --production. For applications (not libraries), the distinction still matters: build tools should be devDependencies to keep production Docker images lean. Common mistake: putting TypeScript, @types/*, and ESLint in dependencies instead of devDependencies.

4. What are peer dependencies and when do you use them?

peerDependencies declare packages that your package expects to be present in the consumer's project but does NOT install automatically. They are used when your package is a plugin or extension of another package. Example: a React component library declares react and react-dom as peerDependencies. Why: if the library installed its own React, the app would have two copies of React running, breaking hooks (which depend on a singleton). The consumer provides the single shared React instance. Before npm 7, peer deps generated warnings only; npm 7+ installs them automatically (but fails on version conflicts). peerDependenciesMeta lets you mark specific peer deps as optional. Pattern: anything your plugin "plugs into" should be a peer dependency.

5. How does npm handle duplicate packages / dependency deduplication?

npm hoists shared dependencies to the top-level node_modules when possible. If package A and B both need lodash@4.17.21, npm installs one copy at node_modules/lodash. But if A needs lodash@3.x and B needs lodash@4.x, both copies are installed - lodash@4 at the top level and lodash@3 nested in node_modules/A/node_modules/lodash. This is flat deduplication. npm dedupe optimizes further by finding compatible versions that satisfy all requirements and removing duplicates. You can also use npm overrides to force all nested packages to use a specific version (useful for security fixes). pnpm handles this differently: it uses symlinks to a central store, never duplicating files on disk.

6. What is npm audit and how do you handle security vulnerabilities?

npm audit checks your dependency tree against the npm Advisory Database (which aggregates CVE data) and reports vulnerabilities by severity: critical, high, moderate, low. Run npm audit to see issues; npm audit fix to auto-upgrade vulnerable packages within their semver range; npm audit fix --force to apply breaking updates if needed. For false positives or acceptable risks, npm audit uses .npmrc: audit-level=high (only fail on high+). In CI, fail the build on high/critical: npm audit --audit-level=high. The overrides field in package.json can pin a vulnerable transitive dependency to a safe version even when the parent hasn't released a fix yet. Keep Dependabot enabled on GitHub for automated PR-based fixes.

7. What is the difference between npm install and npm ci?

npm install reads package.json ranges, resolves compatible versions, installs packages, and updates package-lock.json if needed. It is forgiving and flexible - good for development where you want the latest compatible versions. npm ci (clean install) reads package-lock.json exclusively and installs exactly those pinned versions. It deletes node_modules first, never modifies the lock file, and fails immediately if package-lock.json does not exist or does not match package.json. npm ci is faster (no resolution step), deterministic, and ideal for CI/CD pipelines. Rule of thumb: use npm install locally, use npm ci in CI environments.

8. How do npm workspaces help with monorepos?

npm workspaces (added in npm 7) allow managing multiple packages in a single repository with a shared node_modules. Define workspaces in the root package.json: "workspaces": ["packages/*", "apps/*"]. npm install from the root hoists shared dependencies, installs cross-workspace dependencies as symlinks, and deduplicates. This means apps/web can import @myorg/utils (a local package) without publishing it. You run scripts with npm run build --workspace=apps/web or across all packages with npm run test --workspaces. Compared to tools like Lerna (largely superseded) or Turborepo (caching layer on top of workspaces), npm workspaces handle installation and basic scripting; Turborepo adds incremental builds and caching.

9. How does semantic versioning protect consumers?

Semver (major.minor.patch) is a social contract between library authors and consumers. A patch bump (1.4.2 → 1.4.3) means only bug fixes - safe to auto-update. A minor bump (1.4.2 → 1.5.0) means new features added backward-compatibly - safe to auto-update. A major bump (1.4.2 → 2.0.0) means breaking changes - requires manual review and migration. The caret range "^1.4.2" allows npm to auto-install minor and patch updates (staying on major 1.x), trusting the library author's semver promises. This breaks when authors introduce accidental breaking changes in minor/patch releases (unfortunately common). For production stability, some teams use save-exact: true in .npmrc to pin all versions exactly.

10. What are dist-tags and how do you use them in a release workflow?

Dist-tags are human-readable labels pointing to specific package versions in the npm registry. The default tag is latest - this is what npm install pkg gives you. When publishing a prerelease, use npm publish --tag beta or --tag next to avoid updating latest. Users can then opt into prereleases explicitly: npm install pkg@beta. This is critical for not accidentally forcing users onto unstable versions. You can also use tags for LTS releases: npm dist-tag add pkg@1.10.5 lts-v1. Consumers pin to npm install pkg@lts-v1 and get updates within that line. When you are ready to promote a prerelease to stable: npm dist-tag add pkg@2.0.0-rc.3 latest - instantly all npm install pkg installs it.

npm

Fundamentals & package.json

npm: Fundamentals & package.json npm is the default package manager for Node.js. It ships with Node.js and manages packages (libraries) for your project. packag

npm: Fundamentals & package.json

npm is the default package manager for Node.js. It ships with Node.js and manages packages (libraries) for your project. package.json is the manifest file describing your project and its dependencies.

npm vs yarn vs pnpm

  • npm: built into Node.js, most universal, v10+ is fast. Lock file: package-lock.json.

  • yarn (classic): was faster than npm in 2016, introduced yarn.lock. yarn berry (v2+): PnP mode, corepack — complex, avoid unless you know why.

  • pnpm: fastest, most disk-space-efficient (hard-links), strict about phantom dependencies. Lock file: pnpm-lock.yaml. Excellent for monorepos.

  • Recommendation: pnpm for new projects; npm for maximum compatibility; avoid yarn classic in new codebases.

package.json — Key Fields

{
  "name": "@myorg/my-package",
  "version": "1.2.3",
  "description": "Short description of what this does",
  "license": "MIT",
  "author": "Alice <alice@example.com>",

  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "types": "./dist/index.d.ts"
    },
    "./utils": {
      "import": "./dist/utils.mjs",
      "types": "./dist/utils.d.ts"
    }
  },
  "types": "./dist/index.d.ts",

  "files": ["dist", "!dist/**/*.test.*"],

  "scripts": {
    "build": "tsc",
    "test": "vitest",
    "lint": "eslint src"
  },

  "engines": {
    "node": ">=18.0.0",
    "npm": ">=9.0.0"
  },

  "dependencies": {
    "zod": "^3.22.0"
  },
  "devDependencies": {
    "typescript": "^5.3.0",
    "vitest": "^1.0.0"
  },
  "peerDependencies": {
    "react": ">=18.0.0"
  },
  "peerDependenciesMeta": {
    "react": { "optional": true }
  },

  "keywords": ["utility", "typescript"],
  "repository": {
    "type": "git",
    "url": "https://github.com/myorg/my-package.git"
  },
  "bugs": "https://github.com/myorg/my-package/issues",
  "homepage": "https://mypackage.dev"
}

Semantic Versioning

Version: MAJOR.MINOR.PATCH  (e.g. 2.4.1)

MAJOR — breaking changes
MINOR — new features, backward compatible
PATCH — bug fixes

Range specifiers in package.json:
  "^3.4.1"   — compatible (same major): >=3.4.1 <4.0.0  ← default npm behavior
  "~3.4.1"   — approximate (same minor): >=3.4.1 <3.5.0
  "3.4.1"    — exact version pinned
  ">=3.4.1"  — at least this version
  "*"        — any version (dangerous)
  "3.x"      — any 3.x.x
  "3.4.x"    — any 3.4.x

Pre-release tags:
  "1.0.0-alpha.1"
  "1.0.0-beta.2"
  "1.0.0-rc.1"

package-lock.json

The lock file records exact versions of every installed package (including transitive dependencies). It ensures reproducible installs across machines and CI. Always commit it to version control. Never edit it manually.

  • npm install: installs what package.json specifies, may update lock file

  • npm ci: installs EXACTLY what lock file specifies (fails if package.json and lock mismatch). Use in CI.

  • Lock file conflict in PRs: run npm install after resolving package.json conflict, then commit the regenerated lock file.

npm

Managing Dependencies & Scripts

npm: Managing Dependencies & Scripts Installing Packages # Install all dependencies npm install npm ci # CI — strict, uses lock file exactly # Add a dependency

npm: Managing Dependencies & Scripts

Installing Packages

# Install all dependencies
npm install
npm ci                          # CI — strict, uses lock file exactly

# Add a dependency
npm install express             # adds to "dependencies"
npm install typescript --save-dev   # adds to "devDependencies"
npm install -D vitest           # shorthand for --save-dev

# Install specific version or tag
npm install react@18.2.0
npm install react@latest
npm install react@beta

# Install without saving to package.json
npm install --no-save some-tool

# Install globally (CLI tools)
npm install -g vercel
npm install -g typescript

# Install from GitHub
npm install github:expressjs/express
npm install expressjs/express#v5.0.0   # from specific tag

Updating & Removing

# View outdated packages
npm outdated

# Update packages (respects semver ranges in package.json)
npm update                      # all packages
npm update express              # specific package

# Update to latest (ignores semver range)
npm install express@latest

# Use npm-check-updates to bump package.json ranges
npx npm-check-updates           # show what would change
npx npm-check-updates -u        # update package.json
npm install                     # install updated versions

# Remove package
npm uninstall lodash
npm uninstall -g old-cli-tool

# Clean install (delete node_modules and reinstall)
rm -rf node_modules && npm ci

npm Scripts

Scripts in package.json run via npm run <name>. Special names run without the "run" keyword: npm start, npm test, npm build. Pre/post hooks run automatically.

{
  "scripts": {
    "build": "tsc -p tsconfig.json",
    "prebuild": "rm -rf dist",
    "postbuild": "node scripts/copy-assets.js",

    "start": "node dist/server.js",
    "dev": "tsx watch src/server.ts",

    "test": "vitest",
    "test:watch": "vitest --watch",
    "test:coverage": "vitest --coverage",

    "lint": "eslint src --ext .ts,.tsx",
    "lint:fix": "eslint src --ext .ts,.tsx --fix",
    "format": "prettier --write src",

    "typecheck": "tsc --noEmit",

    "check": "npm run typecheck && npm run lint && npm run test",

    "db:generate": "drizzle-kit generate",
    "db:migrate": "tsx src/db/migrate.ts",

    "release": "npm run check && npm run build && npm publish"
  }
}
# Run scripts
npm run build
npm run test:coverage
npm test          # shorthand for npm run test
npm start         # shorthand for npm run start

# Pass args to script after --
npm test -- --reporter verbose
npm run build -- --watch

# Run multiple scripts
npm run lint && npm run test     # sequential
npm run lint & npm run test      # parallel (Unix)

# Use npm-run-all for cross-platform parallel/sequential
npm install -D npm-run-all
# "check": "run-p lint typecheck"          (parallel)
# "build": "run-s clean compile copy"       (sequential)

npx — Run Without Installing

# Run a CLI tool without globally installing it
npx create-next-app@latest my-app
npx prisma migrate dev
npx tsx scripts/seed.ts

# Force fresh download (don't use cached)
npx --yes create-react-app my-app

# Run a specific version
npx typescript@5.3.0 --version

Dependency Types

  • dependencies: required at runtime — shipped in production bundle

  • devDependencies: build tools, test frameworks, linters — not included in production

  • peerDependencies: tells consumers what version of a shared dep is expected (e.g., React for a UI library). Consumers must install it themselves.

  • optionalDependencies: install fails gracefully if this package can't install (e.g., platform-specific native modules)

  • overrides: force a specific version of a transitive dependency (npm v8+)

{
  "overrides": {
    "semver": "^7.5.4",
    "postcss": {
      "postcss-loader": "^7.0.0"
    }
  }
}

Local Package Linking

# Link a local package for development
cd my-library
npm link

cd my-app
npm link my-library    # Now node_modules/my-library → your local code

# Unlink
npm unlink my-library

# Alternative: file: protocol in package.json (simpler, works with lock file)
# "my-library": "file:../my-library"
npm install
npm

Publishing Packages to npm Registry

npm: Publishing Packages Authentication # Login to npm registry npm login # prompts for username/password/OTP npm login --scope=@myorg # login for org scope # C

npm: Publishing Packages

Authentication

# Login to npm registry
npm login                       # prompts for username/password/OTP
npm login --scope=@myorg        # login for org scope

# Check current user
npm whoami

# Logout
npm logout

# Use access token (for CI)
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
# Or set NPM_TOKEN env variable — some registries read it automatically

Package Scope & Access

  • Unscoped packages: published as "my-package", globally unique name

  • Scoped packages: "@myorg/my-package" — org or username prefix, must be published explicitly as public

  • Public packages: anyone can install. Private packages: npm Pro/Teams required (paid).

  • GitHub Packages: alternative registry, free for public packages, integrates with GitHub Actions

Preparing to Publish

# Check what will be included in the published package
npm pack --dry-run
npm pack              # Creates a .tgz file to inspect

# Verify package contents
tar -tzf my-package-1.0.0.tgz | head -50
// Control what's published via "files" field (whitelist)
{
  "files": [
    "dist",
    "README.md",
    "!dist/**/*.test.*",
    "!dist/**/*.map"
  ]
}

// Always excluded: .git, node_modules, .npmrc, .env, *.log
// Always included: package.json, README.md, LICENSE, CHANGELOG.md

Publishing

# Publish for the first time
npm publish                              # defaults to latest tag
npm publish --access public              # required for scoped packages

# Publish with a tag (won't be installed by default with @latest)
npm publish --tag beta
npm publish --tag next

# Install specific tag
npm install my-package@beta
npm install my-package@next

Versioning Workflow

# Bump version (updates package.json, creates git tag)
npm version patch    # 1.0.0 → 1.0.1
npm version minor    # 1.0.0 → 1.1.0
npm version major    # 1.0.0 → 2.0.0

npm version 1.2.3    # set exact version
npm version prerelease --preid beta   # 1.0.0 → 1.0.1-beta.0

# Typical release workflow
npm run check        # lint + test + typecheck
npm run build        # compile
npm version patch    # bump version, creates git tag
git push && git push --tags
npm publish

# Or use release tools
npx release-it       # interactive release workflow
npx changeset        # monorepo-friendly changelog management

dist-tags

# Manage distribution tags
npm dist-tag ls my-package          # show all tags
npm dist-tag add my-package@1.0.0 latest
npm dist-tag add my-package@2.0.0-beta.1 beta
npm dist-tag rm my-package old-tag

# Users install 'latest' by default:
npm install my-package       # installs 'latest' tag
npm install my-package@beta  # installs 'beta' tag

Package Deprecation

# Deprecate a version (warning shown on install)
npm deprecate my-package@"< 2.0.0" "Please upgrade to v2.0.0+"

# Deprecate all versions
npm deprecate my-package "This package is no longer maintained"

# Unpublish (only within 72 hours of publish, strict policy)
npm unpublish my-package@1.0.0    # specific version
# After 72h: contact npm support
# Better alternative: deprecate instead of unpublish

GitHub Packages Registry

# .npmrc to use GitHub registry for a scope
@myorg:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

# Publish to GitHub Packages
npm publish --registry https://npm.pkg.github.com

# package.json name must match GitHub org
# "@myorg/my-package" → github.com/myorg/my-package
npm

Security, Auditing & Best Practices

npm: Security, Auditing & Best Practices npm audit # Check for known vulnerabilities npm audit # full report npm audit --production # only production dependenci

npm: Security, Auditing & Best Practices

npm audit

# Check for known vulnerabilities
npm audit                     # full report
npm audit --production        # only production dependencies
npm audit --json              # JSON output for CI parsing
npm audit --audit-level high  # exit 1 only for high/critical

# Fix automatically (safe fixes — no semver range violations)
npm audit fix

# Fix including breaking changes (review carefully)
npm audit fix --force

# Ignore specific vulnerability in CI
# Add to package.json:
# "auditConfig": { "ignore": ["GHSA-xxxx-xxxx-xxxx"] }
# Or use: https://github.com/quinnluke/npm-audit-resolver

Supply Chain Security

  • Typosquatting: npm install lodash (ok) vs npm install loadash (malicious). Always double-check package names.

  • Lock file poisoning: attacker modifies lock file in PRs. Review lock file diffs in code review.

  • Dependency confusion: internal package names can be hijacked on public registry. Use scoped packages (@myorg/...) and registry overrides.

  • Script injection: package install scripts (preinstall/postinstall) can run arbitrary code. Use --ignore-scripts when auditing.

  • Provenance: npm now supports provenance attestations — cryptographic proof of where a package was built.

Token Management

# Create tokens
npm token create                          # full publish token (avoid)
npm token create --read-only              # install-only token
npm token create --cidr-whitelist=10.0.0.0/8  # IP restricted

# List tokens
npm token list

# Revoke a token
npm token revoke <token-id>

# Enable 2FA for publishing (strongly recommended)
npm profile enable-2fa auth-and-writes   # require OTP for publish
npm profile enable-2fa auth-only         # OTP for login only

# CI: use CIDR-restricted or granular access tokens, not your personal full token

.npmrc Configuration

# Project-level .npmrc (commit this)
# Specify registry
registry=https://registry.npmjs.org/

# Use specific Node.js engine
engine-strict=true

# Don't run package install scripts (safer for CI)
ignore-scripts=false   # set to true if you trust your deps

# Scoped package registry
@myorg:registry=https://registry.myorg.com/
//registry.myorg.com/:_authToken=${MY_REGISTRY_TOKEN}

# System-level ~/.npmrc (DON'T commit this — has tokens)
//registry.npmjs.org/:_authToken=npm_abc123...

npm-check-updates

# Check what can be updated (doesn't modify anything)
npx npm-check-updates

# Update package.json with new ranges
npx npm-check-updates -u

# Update only minor/patch
npx npm-check-updates --target minor -u

# Exclude specific packages
npx npm-check-updates --reject react,typescript -u

# After updating package.json
npm install    # installs new versions and updates lock file

Workspaces (Monorepos)

// Root package.json
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["packages/*", "apps/*"]
}
# Install all workspace dependencies
npm install

# Run script in specific workspace
npm run build --workspace packages/ui
npm run test --workspace apps/web

# Run script in all workspaces
npm run build --workspaces

# Add dependency to specific workspace
npm install lodash --workspace packages/utils

# Add dependency from one workspace to another
npm install @myorg/ui --workspace apps/web

Best Practices

  • Always commit package-lock.json — reproducible builds are critical

  • Use npm ci in CI pipelines — faster, strict, never modifies lock file

  • Run npm audit in CI and fail builds on high/critical vulnerabilities

  • Enable 2FA on your npm account — supply chain attacks target maintainer accounts

  • Use scoped package names (@yourorg/pkg) — prevents dependency confusion attacks

  • Keep devDependencies separate from dependencies — don't bloat production installs

  • Review lock file diffs in PRs — large unexpected changes are a red flag

  • Set "engines" field — documents and optionally enforces Node.js version requirements

  • Use "files" whitelist in package.json — never accidentally publish .env or secrets

Keep your npm knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever