Skip to main content

Node.js Package Management

What Is a Package Manager?

A tool for installing, managing, and sharing external libraries (packages) in the Node.js ecosystem. Package metadata is recorded in package.json, and the actual code is stored in node_modules/.


npm vs yarn vs pnpm Comparison

Featurenpmyarn (v1 Classic)pnpm
Released201020162017
BundledWith Node.jsSeparate installSeparate install
Install speedAverageFast (parallel)Very fast
Disk usageHighHighLow (symlinks)
WorkspacesSupportedSupportedSupported (excellent)
SecurityAverageStricterStrict
Lock filepackage-lock.jsonyarn.lockpnpm-lock.yaml

Command Comparison

# Install a package
npm install express
yarn add express
pnpm add express

# Dev dependency
npm install --save-dev jest
yarn add --dev jest
pnpm add -D jest

# Install all dependencies (CI)
npm ci
yarn install --frozen-lockfile
pnpm install --frozen-lockfile

# Run a script
npm run build
yarn build
pnpm build

# Global install
npm install -g pm2
yarn global add pm2
pnpm add -g pm2

# Remove a package
npm uninstall express
yarn remove express
pnpm remove express

# Update
npm update
yarn upgrade
pnpm update

How pnpm Saves Disk Space

npm/yarn approach:
project-a/node_modules/lodash@4.17.21/ (copy)
project-b/node_modules/lodash@4.17.21/ (copy)
project-c/node_modules/lodash@4.17.21/ (copy)

pnpm approach:
~/.pnpm-store/lodash@4.17.21/ (stored only once)
project-a/node_modules/lodash → symbolic link
project-b/node_modules/lodash → symbolic link
project-c/node_modules/lodash → symbolic link

package.json Fields Explained

{
"name": "my-awesome-app",
"version": "1.2.3",
"description": "Node.js application example",
"main": "dist/index.js",
"module": "dist/index.esm.js",
"exports": {
".": {
"import": "./dist/index.esm.js",
"require": "./dist/index.cjs.js"
},
"./utils": "./dist/utils.js"
},
"type": "module",
"scripts": {
"start": "node dist/index.js",
"dev": "nodemon src/index.js",
"build": "tsc",
"test": "jest --coverage",
"test:watch": "jest --watch",
"lint": "eslint src/**/*.js",
"lint:fix": "eslint src/**/*.js --fix",
"format": "prettier --write src/**/*.js",
"prepare": "husky install",
"prepublishOnly": "npm run build && npm test"
},
"keywords": ["node", "express", "api"],
"author": {
"name": "John Doe",
"email": "john@example.com",
"url": "https://example.com"
},
"license": "MIT",
"homepage": "https://github.com/user/repo#readme",
"repository": {
"type": "git",
"url": "https://github.com/user/repo.git"
},
"bugs": {
"url": "https://github.com/user/repo/issues"
},
"engines": {
"node": ">=20.0.0",
"npm": ">=10.0.0"
},
"dependencies": {
"express": "^4.18.2",
"dotenv": "^16.3.1"
},
"devDependencies": {
"jest": "^29.7.0",
"nodemon": "^3.0.2",
"eslint": "^8.55.0"
},
"peerDependencies": {
"react": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "^2.3.3"
},
"files": [
"dist",
"README.md"
],
"private": true
}

Key Field Descriptions

  • name: Package name, must be unique in the npm registry (scopes allowed: @myorg/pkg)
  • version: SemVer version (major.minor.patch)
  • main: CommonJS require() entry point
  • module: ESM import entry point (for bundlers)
  • exports: Precise entry point control (Node.js 12+)
  • type: When set to "module", .js files are treated as ESM
  • files: List of files to include when publishing to npm
  • private: When true, prevents accidental publishing

Dependency Types

{
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"jest": "^29.7.0",
"nodemon": "^3.0.2",
"typescript": "^5.3.2",
"eslint": "^8.55.0"
},
"peerDependencies": {
"react": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "^2.3.3"
}
}
TypeInstall conditionPurpose
dependenciesAlwaysPackages required at runtime
devDependenciesnpm install (excluded with --production)Development/build tools
peerDependenciesManual (user installs)Declares compatible versions when authoring a library
optionalDependenciesIgnored on failurePlatform-specific optional dependencies
# Install production only (excluding devDependencies)
npm install --production
# or NODE_ENV=production npm install

Version Range Specifiers

SemVer (Semantic Versioning): major.minor.patch

  • major: Incompatible API changes (breaking change)
  • minor: Backwards-compatible new features
  • patch: Bug fixes
{
"dependencies": {
"pkg-a": "4.18.2", // Exactly this version
"pkg-b": "^4.18.2", // 4.x.x (major fixed)
"pkg-c": "~4.18.2", // 4.18.x (minor fixed)
"pkg-d": ">=4.0.0", // 4.0.0 or higher
"pkg-e": ">=4.0.0 <5.0.0",// 4.x.x range
"pkg-f": "*", // Latest version (not recommended)
"pkg-g": "latest", // Latest version tag
"pkg-h": "4.x", // 4.x.x
"pkg-i": "git+https://github.com/user/repo.git", // Git URL
"pkg-j": "file:../local-package" // Local path
}
}
# Check currently installed versions
npm list --depth=0

# Check for updatable packages
npm outdated

# Safe updates (patch, minor)
npm update

# Major update (use with caution)
npm install express@latest

# Check for vulnerabilities
npm audit

# Auto-fix
npm audit fix

The Role of package-lock.json and yarn.lock

package.json      → "Roughly this version is fine" (range specifier)
package-lock.json → "Use exactly this version" (pinned)

package-lock.json Example

{
"name": "my-app",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-...",
"dependencies": {
"accepts": "~1.3.8",
"body-parser": "1.20.1"
}
}
}
}

Important rules:

  • package-lock.json must always be committed to git
  • node_modules/ must be added to .gitignore
  • Use npm ci in CI/CD (strictly follows the lock file)

Using npm Scripts

{
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js --watch src",
"build": "tsc -p tsconfig.json",
"clean": "rm -rf dist",
"prebuild": "npm run clean",
"postbuild": "echo Build complete",
"test": "jest",
"test:ci": "jest --ci --coverage --forceExit",
"lint": "eslint src",
"format": "prettier --write 'src/**/*.{js,ts}'",
"typecheck": "tsc --noEmit",
"check": "npm run lint && npm run typecheck && npm test",
"docker:build": "docker build -t myapp .",
"docker:run": "docker run -p 3000:3000 myapp",
"db:migrate": "node scripts/migrate.js",
"db:seed": "node scripts/seed.js",
"db:reset": "npm run db:migrate && npm run db:seed"
}
}

Special Scripts

# pre/post hooks
# prebuild → build → postbuild run automatically in order

# Passing environment variables
NODE_ENV=production npm run build

# Passing arguments to a script
npm run test -- --verbose --testNamePattern="User"
# jest --verbose --testNamePattern="User"

# Running local packages with npx
npx jest
npx ts-node src/index.ts

Local vs Global Packages

# Local install (project dependency)
npm install express
# → stored in node_modules/express/
# → available only within the project

# Global install (system tool)
npm install -g pm2 nodemon typescript
# → stored in global path (check with: which pm2)
# → usable as CLI from anywhere

# Check globally installed packages
npm list -g --depth=0

# Global install path
npm root -g
npm bin -g

# Recommended: use npx instead of global install
npx create-next-app my-app
npx nodemon server.js

.npmrc Configuration

# .npmrc (project root or home directory)

# Registry setting
registry=https://registry.npmjs.org/

# Per-scope registry (private registry)
@mycompany:registry=https://npm.mycompany.com/

# Log level
loglevel=warn

# Save behavior
save-exact=true # Save exact version without ^
save-prefix=~ # Use ~ instead of ^

# Cache path
cache=/tmp/npm-cache

# Proxy settings (corporate environment)
# proxy=http://proxy.company.com:8080
# https-proxy=http://proxy.company.com:8080

# Package lock
package-lock=true

# Offline mode
# offline=true

# Auth token (GitHub Packages, etc.)
# //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

Pro Tips

workspaces (Monorepo)

// Root package.json
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*",
"apps/*"
]
}
# Install a package in a specific workspace
npm install express --workspace=apps/api

# Run a script in a specific workspace
npm run build --workspace=packages/ui

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

Package Security Audit

# Check for vulnerabilities
npm audit

# Output in JSON format
npm audit --json

# Auto-fix patchable vulnerabilities
npm audit fix

# Force fix (caution: includes major updates)
npm audit fix --force

# Ignore a specific package vulnerability (temporary)
# Configure in .nsprc or .npmrc