npm & Package Management
npm is the world's largest software registry. Understanding it saves you from dependency hell.
What npm Actually Is
npm has two parts that often get confused:
- npm CLI — a command-line tool that ships with Node.js. You use it to install, update, and manage packages.
- npm Registry — a public database at
registry.npmjs.orghosting over 2 million packages.
package.json — Your Project's Identity
Every Node.js project has a package.json. It's the manifest — it declares your project's name, version, dependencies, and scripts.
{
"name": "my-api",
"version": "1.0.0",
"description": "A REST API for my app",
"main": "src/index.js", // entry point
"scripts": {
"start": "node src/index.js", // npm start
"dev": "nodemon src/index.js", // npm run dev
"test": "jest", // npm test
"lint": "eslint src/" // npm run lint
},
"dependencies": {
"express": "^4.18.2" // needed in production
},
"devDependencies": {
"nodemon": "^3.0.1", // only needed during development
"jest": "^29.6.0"
}
}
Dependencies vs devDependencies
dependencies
Packages needed to run your app in production.
Express, Prisma, bcrypt, jsonwebtoken, zod — anything your server imports at runtime.
npm install express
devDependencies
Packages only needed during development — stripped out in production builds.
nodemon, jest, eslint, typescript — tooling, not runtime code.
npm install -D nodemon
Semantic Versioning (SemVer)
Every npm package version follows the MAJOR.MINOR.PATCH format:
The prefix characters in package.json control what versions are allowed:
| Prefix | Example | Meaning |
|---|---|---|
^ | ^4.18.2 | Accept MINOR and PATCH updates. MAJOR stays at 4. (Default) |
~ | ~4.18.2 | Accept PATCH updates only. More conservative. |
| exact | 4.18.2 | Locked to this exact version. Maximum reproducibility. |
The Lock File
package-lock.json records the exact version of every package (and every sub-dependency) that was installed. This guarantees every developer on your team — and your production server — installs identical versions.
package-lock.json to git. Never commit node_modules/ — add it to .gitignore. Anyone cloning your repo runs npm ci to reproduce the exact locked environment.
Essential Commands
# Initialise a new project (creates package.json)
npm init -y
# Install a production dependency
npm install express
# Install a dev dependency
npm install -D nodemon
# Install all dependencies from package.json (for new clones)
npm install
# Install from lock file — exact versions, no updates (use in CI/production)
npm ci
# Remove a package
npm uninstall express
# Check for outdated packages
npm outdated
# Update packages (respects semver ranges in package.json)
npm update
# Audit for known security vulnerabilities
npm audit
# Run a script defined in package.json
npm run dev
npm test # shorthand for 'npm run test'
npm start # shorthand for 'npm run start'
node_modules Explained
When you npm install, packages are downloaded to node_modules/. This directory can be enormous (hundreds of MB) because each package also installs its own dependencies recursively.
📁 node_modules/ — never commit this
📄 package.json — always commit this
🔒 package-lock.json — always commit this
🧠 Check Your Understanding
Go Deeper
Primary source: npm docs — package.json
Also read: semver.org — the full spec in plain English.
Ask your teacher: "What does npm ci do differently than npm install?" or "Why does node_modules get so large?"