npm & package.json: Dependency Management Mastered

npm & package.json: Dependency Management Mastered
Every Node.js project begins with npm init. The resulting package.json becomes the single source of truth for your project — its identity, its scripts, and every package it depends on. npm (Node Package Manager) then manages the entire lifecycle: installing, updating, auditing, and publishing packages.
Misunderstanding npm leads to subtle bugs: inconsistent environments across machines, bloated production images, missing lockfiles, and security vulnerabilities you never knew existed. This module fixes all of that.
This is Module 8 of the Node.js Full‑Stack Developer course.
Initialising a Project
Every Node.js project starts with a package.json. Create one with:
# Interactive — asks questions
npm init
# Skip questions, accept all defaults
npm init -yThe -y flag accepts all defaults and produces a minimal package.json:
{
"name": "my-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}The package.json Fields Explained
Here is a production-ready package.json with every important field annotated:
{
"name": "my-api",
"version": "2.1.0",
"description": "REST API for MyApp",
"main": "src/index.js",
"type": "module",
"engines": {
"node": ">=20.0.0",
"npm": ">=10.0.0"
},
"scripts": {
"start": "node src/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",
"db:migrate": "node src/db/migrate.js"
},
"dependencies": {
"express": "^4.19.2",
"mongoose": "^8.3.0",
"jsonwebtoken": "^9.0.2",
"bcrypt": "^5.1.1",
"dotenv": "^16.4.5"
},
"devDependencies": {
"jest": "^29.7.0",
"supertest": "^7.0.0",
"nodemon": "^3.1.0",
"eslint": "^9.1.0"
},
"keywords": ["api", "rest", "express"],
"author": "TopicTrick Team",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/myorg/my-api"
}
}Key Fields
| Field | Purpose |
|---|---|
name | Package identifier — must be lowercase, no spaces |
version | Current version following semantic versioning |
main | Entry point when this package is require()d |
type | "module" for ES Modules, omit for CommonJS |
engines | Specifies compatible Node/npm versions |
scripts | Custom commands run with npm run <name> |
dependencies | Packages needed in production |
devDependencies | Packages only needed during development |
private | Set true to prevent accidental publish to npm registry |
Installing Packages
Install a Production Dependency
npm install express
# or shorthand:
npm i expressThis downloads Express into node_modules/ and adds it to dependencies in package.json.
Install a Dev Dependency
npm install --save-dev jest
# or shorthand:
npm i -D jestAdds to devDependencies. These are skipped in production installs.
Install a Specific Version
npm install express@4.18.2 # exact version
npm install express@^4.18.0 # latest 4.x.x
npm install express@latest # latest versionInstall All Dependencies
# Install everything in package.json
npm install
# Install only production dependencies (for deployment)
npm install --omit=devInstall a Global Package
npm install -g nodemon # available as a CLI command everywhere
npm install -g typescript # tsc available globallyGlobal packages are for CLI tools, not project dependencies. Never npm install -g an API library.
Uninstall a Package
npm uninstall express # removes from node_modules and package.json
npm uninstall -g nodemon # remove global packageSemantic Versioning (semver)
Every npm package version follows semver: MAJOR.MINOR.PATCH
2 . 1 . 4
│ │ └── PATCH: bug fixes, backwards compatible
│ └────────── MINOR: new features, backwards compatible
└────────────────── MAJOR: breaking changesVersion Range Prefixes in package.json
{
"dependencies": {
"express": "4.18.2", // exact — only this version
"express": "~4.18.2", // tilde — patch updates only (4.18.x)
"express": "^4.18.2", // caret — minor + patch updates (4.x.x)
"express": "*", // any version (dangerous)
"express": ">=4.0.0" // range
}
}^ is the default when you run npm install. It allows minor updates which should be backwards compatible by semver convention — but in practice, always test after updating.
Checking and Updating Versions
# See which packages are outdated
npm outdated
# Update all packages to their latest allowed version (respects semver ranges)
npm update
# Update a specific package to the latest version (ignores semver range)
npm install express@latestThe Lockfile: package-lock.json
When you run npm install, npm creates (or updates) package-lock.json. This file records the exact resolved version of every package and every transitive dependency in your entire tree.
{
"name": "my-api",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-...",
"dependencies": {
"accepts": "~1.3.8",
...
}
}
}
}Rules for the Lockfile
- Always commit
package-lock.jsonto version control - Never edit it manually — npm manages it automatically
- Use
npm ci(notnpm install) in CI/CD — it installs exactly what the lockfile specifies and fails if it does not matchpackage.json
# Development — updates lockfile if package.json changed
npm install
# CI/CD — strict install from lockfile, never updates it
npm cinpm Scripts
The scripts field in package.json is one of its most powerful features. It lets you define project-specific commands that everyone on the team can run the same way, without globally installing tools.
Running Scripts
npm run dev # runs the "dev" script
npm run test # or shorthand:
npm test # (test, start, and stop don't need "run")
npm startBuilt-in Lifecycle Scripts
npm automatically runs certain scripts at specific times:
{
"scripts": {
"preinstall": "echo 'Before install'",
"install": "...",
"postinstall": "echo 'After install'",
"pretest": "npm run lint",
"test": "jest",
"posttest": "echo 'Tests complete'",
"prebuild": "npm run lint",
"build": "tsc",
"prepare": "husky install" // runs after npm install and before publish
}
}Any script prefixed with pre or post runs automatically before or after the matching script.
Practical Script Patterns
{
"scripts": {
"start": "node dist/index.js",
"dev": "nodemon src/index.js --watch src",
"build": "tsc --outDir dist",
"clean": "rm -rf dist",
"prebuild": "npm run clean",
"test": "jest --coverage --forceExit",
"test:watch": "jest --watch",
"test:ci": "jest --coverage --ci --forceExit",
"lint": "eslint src --ext .js,.ts",
"lint:fix": "eslint src --ext .js,.ts --fix",
"format": "prettier --write 'src/**/*.{js,ts}'",
"db:seed": "node scripts/seed.js",
"db:reset": "node scripts/reset.js"
}
}Passing Arguments to Scripts
# Pass extra args with -- separator
npm test -- --testPathPattern=user
# Runs: jest --coverage --forceExit --testPathPattern=userCross-Platform Scripts with cross-env
Environment variables in scripts work differently on Windows vs macOS/Linux:
# ❌ Breaks on Windows
"dev": "NODE_ENV=development nodemon src/index.js"
# ✅ Works everywhere
npm install -D cross-env
"dev": "cross-env NODE_ENV=development nodemon src/index.js"Security: npm audit
The npm registry tracks known vulnerabilities in packages. Run an audit regularly:
# Show all vulnerabilities
npm audit
# Auto-fix vulnerabilities where possible
npm audit fix
# Fix including breaking changes (use with care)
npm audit fix --forceAudit Output
found 3 vulnerabilities (1 low, 1 moderate, 1 high)
run `npm audit fix` to fix them, or `npm audit` for detailsIgnoring False Positives
If a vulnerability does not affect your usage:
npm audit --json > audit.json # save for reviewOr add an .nsprc / npm audit exception — but always document why.
Keeping Dependencies Fresh
Set up Dependabot on GitHub to auto-open PRs when packages have updates:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"Useful npm Commands Reference
# Project info
npm ls # list installed packages (flat)
npm ls --depth=0 # list only top-level packages
npm ls express # check if express is installed and its version
npm info express # show registry info about a package
npm home express # open package homepage in browser
# Cache management
npm cache clean --force # clear npm's download cache
npm cache verify # verify cache integrity
# Config
npm config list # show all config values
npm config get registry # show current registry
npm config set registry https://registry.npmjs.org # reset to official registry
# Publishing (for your own packages)
npm login # authenticate with npm registry
npm publish # publish current package
npm version patch # bump patch version and create a git tag
npm version minor
npm version majornpm Workspaces (Monorepos)
If your project has multiple related packages (a monorepo), npm workspaces let you manage them from a single root package.json:
// Root package.json
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/api",
"packages/web",
"packages/shared"
]
}# Install all dependencies for all workspaces
npm install
# Run a script in a specific workspace
npm run build --workspace=packages/api
# Run a script in all workspaces
npm run test --workspacesWorkspaces also automatically link local packages to each other — so packages/api can require('@myapp/shared') without publishing to npm.
.npmrc — Project-Level npm Configuration
Create a .npmrc file in your project root to configure npm behaviour per-project:
# .npmrc
save-exact=true # always pin exact versions (no ^ or ~)
engine-strict=true # fail if Node version doesn't match engines field
fund=false # suppress funding messages
audit-level=high # only fail on high/critical vulnerabilitiesWhat NOT to Commit
Always add these to .gitignore:
node_modules/
.env
.env.local
*.log
dist/
build/
coverage/And always do commit:
package.json ✅
package-lock.json ✅
.npmrc ✅
.nvmrc ✅ (specifies Node version for the project)Your .nvmrc file tells nvm which Node version to use for the project:
# .nvmrc
20.14.0Then anyone on the team runs nvm use to switch to the right version automatically.
Node.js Full‑Stack Course — Module 8 of 32
You now have complete command over npm and package.json. Continue to Module 9 to build your first production web server with Express.js.
Summary
npm and package.json are the operational backbone of every Node.js project. The key points:
npm init -ycreatespackage.json; always customiseengines,scripts, andprivatedependenciesfor runtime;devDependenciesfor tooling — keep them separate- Always commit
package-lock.json; usenpm ciin CI/CD pipelines - Semver:
^allows minor updates,~allows patch updates, no prefix = exact version - Use
npm scriptsas the team's shared command interface — no global tool assumptions - Run
npm auditregularly and use Dependabot to stay on top of security updates - Use
.nvmrcto pin the Node.js version for the project - Never commit
node_modules/— it is reproducible frompackage.json+ lockfile
Continue to Module 9: Introduction to Express.js — Your First Web Server →
Content powered by SearchFit.ai — for automated content at scale, visit https://searchfit.ai
