npm — Package Management
npm (Node Package Manager) is the default package manager for Node.js and the largest software registry in the world. It manages dependencies, runs scripts, publishes packages, and enforces project conventions through a standardized manifest file.
Every Node.js project revolves around three core files: package.json (the manifest), package-lock.json (deterministic lockfile), and .npmrc (configuration). Understanding how these files work together is fundamental to modern JavaScript development.
The package.json file is the entry point of any Node project. It defines metadata, dependencies, scripts, and configuration. Every property serves a specific purpose in the dependency resolution lifecycle.
| 1 | { |
| 2 | "name": "my-app", |
| 3 | "version": "1.0.0", |
| 4 | "description": "A sample Node.js application", |
| 5 | "main": "dist/index.js", |
| 6 | "scripts": { |
| 7 | "start": "node dist/index.js", |
| 8 | "build": "tsc", |
| 9 | "test": "vitest" |
| 10 | }, |
| 11 | "dependencies": { |
| 12 | "express": "^4.18.2", |
| 13 | "zod": "~3.22.0" |
| 14 | }, |
| 15 | "devDependencies": { |
| 16 | "typescript": "^5.4.0", |
| 17 | "vitest": "^1.6.0" |
| 18 | }, |
| 19 | "peerDependencies": { |
| 20 | "react": "^18.0.0" |
| 21 | }, |
| 22 | "optionalDependencies": { |
| 23 | "@img/sharp-darwin-arm64": "^0.33.0" |
| 24 | } |
| 25 | } |
| Field | Purpose | Required |
|---|---|---|
| name | Package name (npm registry key) | ✓ (for publishing) |
| version | SemVer-compliant version string | ✓ (for publishing) |
| scripts | Aliases for CLI commands | — |
| dependencies | Packages required at runtime | — |
| devDependencies | Packages for development only | — |
| peerDependencies | Hosted by consumer (not bundled) | — |
best practice
npm uses semantic versioning (SemVer) with the format MAJOR.MINOR.PATCH. Prefix operators in version ranges control how npm resolves updates within those ranges during install.
| Range | Example | Allowed Updates |
|---|---|---|
| ^ | ^1.2.3 | 1.x.x (major pinned, minor & patch allowed) |
| ~ | ~1.2.3 | 1.2.x (minor pinned, patch allowed) |
| exact | 1.2.3 | Only 1.2.3 (no updates) |
| * | * | Any version |
| >= | >=1.2.0 <2.0.0 | Custom range |
| 1 | { |
| 2 | "dependencies": { |
| 3 | "express": "^4.18.0", // 4.x.x — safe for minor/patch |
| 4 | "react": "~18.2.0", // 18.2.x — only patch updates |
| 5 | "next": "14.1.0", // exact — no auto-updates |
| 6 | "typescript": ">=5.0.0 <6.0.0" // custom range |
| 7 | } |
| 8 | } |
info
The npm install command resolves the dependency tree and installs packages into node_modules. It updates package-lock.json automatically. For deterministic installs, use npm ci which strictly follows the lockfile.
| 1 | # Local install (adds to dependencies) |
| 2 | npm install express |
| 3 | npm install --save-dev typescript # devDependencies |
| 4 | npm install --global http-server # global install |
| 5 | |
| 6 | # Deterministic install (CI/CD) |
| 7 | npm ci |
| 8 | |
| 9 | # Install with exact version |
| 10 | npm install zod@3.22.4 |
| 11 | |
| 12 | # Install from lockfile only |
| 13 | npm ci --only=production |
| 14 | |
| 15 | # Audit after install |
| 16 | npm audit |
| Command | Lockfile | Use Case |
|---|---|---|
| npm install | Updates it | Local development, adding deps |
| npm ci | Ignores it (uses lockfile) | CI/CD, reproducible builds |
warning
npm supports multiple dependency categories, each with a specific installation context and lifecycle. Choosing the correct category is critical for bundle size, security surface, and consumer experience.
dependencies
Runtime packages required for the application to function. Installed in both development and production. Examples: express, react, next.
devDependencies
Tools for development, testing, and building. Not installed in production when using NODE_ENV=production or --production. Examples: typescript, vitest, eslint.
peerDependencies
Packages the consumer must provide. Common in plugins and libraries that expect a specific version of a host framework. Not auto-installed — npm warns if missing.
optionalDependencies
Packages whose installation failure does not block the install. Used for platform-specific binaries. npm continues if the optional dep fails.
| 1 | # Install as dependency |
| 2 | npm install express |
| 3 | |
| 4 | # Install as devDependency |
| 5 | npm install --save-dev typescript |
| 6 | |
| 7 | # Install as optional dependency |
| 8 | npm install --save-optional @img/sharp-darwin-arm64 |
| 9 | |
| 10 | # Install with exact peer (for library authors) |
| 11 | npm install --save-peer react@^18.0.0 |
best practice
The lockfile records the exact version of every installed package, including transitive dependencies. It ensures reproducible installs across machines. Always commit it to version control.
| 1 | { |
| 2 | "name": "my-app", |
| 3 | "lockfileVersion": 3, |
| 4 | "packages": { |
| 5 | "node_modules/express": { |
| 6 | "version": "4.18.2", |
| 7 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", |
| 8 | "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1as+QEJmRg=", |
| 9 | "dependencies": { |
| 10 | "accepts": "~1.3.8", |
| 11 | "array-flatten": "1.1.1" |
| 12 | } |
| 13 | } |
| 14 | } |
| 15 | } |
warning
npm includes built-in security auditing and package publishing workflows. Regular auditing helps catch vulnerable dependencies early in the development cycle.
| 1 | # Scan for vulnerabilities |
| 2 | npm audit |
| 3 | |
| 4 | # Fix vulnerabilities automatically |
| 5 | npm audit fix |
| 6 | |
| 7 | # Fix only patch updates (safer) |
| 8 | npm audit fix --audit-level=moderate |
| 9 | |
| 10 | # Dry-run publish |
| 11 | npm publish --dry-run |
| 12 | |
| 13 | # Publish to registry |
| 14 | npm publish |
| 15 | |
| 16 | # Publish with access scope |
| 17 | npm publish --access public |
| 18 | |
| 19 | # Update version and publish |
| 20 | npm version patch && npm publish |
pro tip
The .npmrc file configures npm behavior at the project, user, or global level. Settings cascade: project-level overrides user-level, which overrides global.
| 1 | # Project-level .npmrc |
| 2 | |
| 3 | # Registry configuration |
| 4 | registry=https://registry.npmjs.org/ |
| 5 | @mycompany:registry=https://npm.mycompany.com/ |
| 6 | |
| 7 | # Authentication |
| 8 | //npm.mycompany.com:_authToken=${NPM_TOKEN} |
| 9 | |
| 10 | # Install behavior |
| 11 | save-exact=true |
| 12 | save-prefix="" |
| 13 | audit-level=high |
| 14 | engine-strict=true |
| 15 | |
| 16 | # Package publish |
| 17 | access=public |
| 18 | tag=latest |
info
npm workspaces enable monorepo management by allowing multiple packages to share a single node_modules and lockfile. This reduces duplication and simplifies cross-package development.
| 1 | { |
| 2 | "name": "my-monorepo", |
| 3 | "private": true, |
| 4 | "workspaces": [ |
| 5 | "packages/*", |
| 6 | "apps/*" |
| 7 | ], |
| 8 | "scripts": { |
| 9 | "build": "npm run build --workspaces", |
| 10 | "test": "npm run test --workspace=@my/app" |
| 11 | } |
| 12 | } |
| 1 | # Install all workspaces |
| 2 | npm install |
| 3 | |
| 4 | # Run script across all workspaces |
| 5 | npm run build --workspaces |
| 6 | |
| 7 | # Run script in specific workspace |
| 8 | npm run test --workspace=@my/app |
| 9 | |
| 10 | # Add dependency to specific workspace |
| 11 | npm install lodash --workspace=@my/app |
| 12 | |
| 13 | # List workspace tree |
| 14 | npm ls --workspaces |
best practice