Node.jsBackendFull-Stack

npm & package.json: Dependency Management Mastered

TT
TopicTrick Team
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:

bash
# Interactive — asks questions
npm init

# Skip questions, accept all defaults
npm init -y

The -y flag accepts all defaults and produces a minimal package.json:

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:

json
{
  "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

FieldPurpose
namePackage identifier — must be lowercase, no spaces
versionCurrent version following semantic versioning
mainEntry point when this package is require()d
type"module" for ES Modules, omit for CommonJS
enginesSpecifies compatible Node/npm versions
scriptsCustom commands run with npm run <name>
dependenciesPackages needed in production
devDependenciesPackages only needed during development
privateSet true to prevent accidental publish to npm registry

Installing Packages

Install a Production Dependency

bash
npm install express
# or shorthand:
npm i express

This downloads Express into node_modules/ and adds it to dependencies in package.json.

Install a Dev Dependency

bash
npm install --save-dev jest
# or shorthand:
npm i -D jest

Adds to devDependencies. These are skipped in production installs.

Install a Specific Version

bash
npm install express@4.18.2        # exact version
npm install express@^4.18.0       # latest 4.x.x
npm install express@latest        # latest version

Install All Dependencies

bash
# Install everything in package.json
npm install

# Install only production dependencies (for deployment)
npm install --omit=dev

Install a Global Package

bash
npm install -g nodemon      # available as a CLI command everywhere
npm install -g typescript   # tsc available globally

Global packages are for CLI tools, not project dependencies. Never npm install -g an API library.

Uninstall a Package

bash
npm uninstall express          # removes from node_modules and package.json
npm uninstall -g nodemon       # remove global package

Semantic Versioning (semver)

Every npm package version follows semver: MAJOR.MINOR.PATCH

text
2   .   1   .   4
│       │       └── PATCH: bug fixes, backwards compatible
│       └────────── MINOR: new features, backwards compatible  
└────────────────── MAJOR: breaking changes

Version Range Prefixes in package.json

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

bash
# 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@latest

The 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.

json
{
  "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.json to version control
  • Never edit it manually — npm manages it automatically
  • Use npm ci (not npm install) in CI/CD — it installs exactly what the lockfile specifies and fails if it does not match package.json
bash
# Development — updates lockfile if package.json changed
npm install

# CI/CD — strict install from lockfile, never updates it
npm ci

npm 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

bash
npm run dev        # runs the "dev" script
npm run test       # or shorthand:
npm test           # (test, start, and stop don't need "run")
npm start

Built-in Lifecycle Scripts

npm automatically runs certain scripts at specific times:

json
{
  "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

json
{
  "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

bash
# Pass extra args with -- separator
npm test -- --testPathPattern=user
# Runs: jest --coverage --forceExit --testPathPattern=user

Cross-Platform Scripts with cross-env

Environment variables in scripts work differently on Windows vs macOS/Linux:

bash
# ❌ 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:

bash
# Show all vulnerabilities
npm audit

# Auto-fix vulnerabilities where possible
npm audit fix

# Fix including breaking changes (use with care)
npm audit fix --force

Audit Output

text
found 3 vulnerabilities (1 low, 1 moderate, 1 high)
  run `npm audit fix` to fix them, or `npm audit` for details

Ignoring False Positives

If a vulnerability does not affect your usage:

bash
npm audit --json > audit.json  # save for review

Or 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:

yaml
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"

Useful npm Commands Reference

bash
# 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 major

npm Workspaces (Monorepos)

If your project has multiple related packages (a monorepo), npm workspaces let you manage them from a single root package.json:

json
// Root package.json
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "packages/api",
    "packages/web",
    "packages/shared"
  ]
}
bash
# 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 --workspaces

Workspaces 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:

ini
# .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 vulnerabilities

What NOT to Commit

Always add these to .gitignore:

text
node_modules/
.env
.env.local
*.log
dist/
build/
coverage/

And always do commit:

text
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:

text
# .nvmrc
20.14.0

Then 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 -y creates package.json; always customise engines, scripts, and private
    • dependencies for runtime; devDependencies for tooling — keep them separate
    • Always commit package-lock.json; use npm ci in CI/CD pipelines
    • Semver: ^ allows minor updates, ~ allows patch updates, no prefix = exact version
    • Use npm scripts as the team's shared command interface — no global tool assumptions
    • Run npm audit regularly and use Dependabot to stay on top of security updates
    • Use .nvmrc to pin the Node.js version for the project
    • Never commit node_modules/ — it is reproducible from package.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