npm Basics
npm is the package manager that ships with Node.js. It downloads libraries from the public registry, records the exact versions in package.json and package-lock.json, runs scripts you've defined, and lets you publish your own packages. Yarn, pnpm and Bun are alternative clients that use the same registry — once you know npm, you mostly know them all.
Starting a project
Inside an empty directory:
npm init # asks questions, writes package.json npm init -y # accept all defaults npm init react-app my-app # use a "create-*" starter
package.json is the manifest. It lists your project's metadata, dependencies, scripts and configuration. Every npm command reads it.
Installing dependencies
npm install lodash # adds to "dependencies" npm install --save-dev jest # adds to "devDependencies" (short: -D) npm install -g typescript # global install (a tool on your PATH) npm install # install everything in package.json
dependencies— packages your code needs at runtime in production.devDependencies— only needed for development: test runners, bundlers, type definitions, linters.peerDependencies— packages your package expects the host project to provide (common for plugins).optionalDependencies— install attempts are made but failures are ignored.
Uninstalling
npm uninstall lodash npm uninstall -D jest npm uninstall -g typescript
Version ranges — caret and tilde
A line like "react": "^18.2.0" is a range, not a fixed version. The leading character controls how flexible the range is.
^1.2.3— caret: allow any version compatible with1.2.3(no breaking change). Matches>=1.2.3 <2.0.0. The default.~1.2.3— tilde: allow patch updates only. Matches>=1.2.3 <1.3.0.1.2.3— exact: only that version.>=1.2.3 <2,1.2.x,*— explicit ranges.
Semantic versioning gives those characters their meaning: MAJOR.MINOR.PATCH — major changes are breaking, minor adds features, patch fixes bugs.
package-lock.json
The lock file records the exact tree of resolved versions for every install. Commit it. Two developers running npm install on the same lock file end up with identical node_modules — no "works on my machine" because of a sneaky minor bump.
Scripts
The scripts field in package.json is a tiny task runner.
package.json (excerpt)
{
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "vitest",
"lint": "eslint ."
}
}npm run dev # named script npm test # short for npm run test npm start # short for npm run start npm run build -- --mode staging # everything after -- is passed through
Scripts run with ./node_modules/.bin already on PATH, so you can invoke any installed CLI by its name without writing the full path.
npx — run binaries without installing them
npx (bundled with npm) runs a CLI from a package. If the package is in your dependencies it uses the local copy; otherwise it downloads it on the fly.
npx prettier --write . # use local prettier if installed npx create-next-app@latest # run a starter without globally installing npx jest # local jest binary from node_modules
Updating dependencies
npm outdated # list packages with newer versions available npm update # update to the latest within the range you specified npm install lodash@latest # upgrade past the range
Workspaces
Workspaces let one repo host multiple packages with one node_modules. Add a workspaces array to the root package.json and you can npm install from any sub-package.
{
"name": "monorepo",
"private": true,
"workspaces": ["packages/*"]
}Security and auditing
npm audit # report known vulnerabilities npm audit fix # apply non-breaking fixes npm audit --json # machine-readable output for CI
Most vulnerabilities reported are in
devDependenciesand never reach production — but read each report.Lock files are the difference between a one-line dependency update and a transitive surprise.