Episode 3 — NodeJS MongoDB Backend Architecture / 3.1 — Starting with NodeJS
3.1.d — NPM Basics
In one sentence: npm (Node Package Manager) is the world's largest software registry and the CLI tool that lets you install, manage, and share reusable JavaScript packages for your Node.js projects.
Navigation: ← 3.1 Overview · Next → 3.1.e Package JSON Deep Dive
Table of Contents
- 1. What Is npm?
- 2. npm vs yarn vs pnpm
- 3. Initializing a Project — npm init
- 4. Installing Packages
- 5. Dependencies vs devDependencies
- 6. The node_modules Folder
- 7. npx — Run Without Installing
- 8. Semantic Versioning (SemVer)
- 9. Managing Packages — Update, Remove, Audit
- 10. npm Scripts
- 11. Security — npm audit
- Key Takeaways
- Explain-It Challenge
1. What Is npm?
npm is actually three things:
┌─────────────────────────────────────────────────────────────────┐
│ npm IS THREE THINGS │
│ │
│ 1. THE REGISTRY (npmjs.com) │
│ └── The world's largest software registry │
│ └── 2M+ packages, 30B+ downloads/week │
│ └── Anyone can publish a package for free │
│ │
│ 2. THE CLI TOOL (npm command) │
│ └── Comes bundled with Node.js │
│ └── Install, update, remove, publish packages │
│ └── Manage project dependencies and scripts │
│ │
│ 3. THE WEBSITE (npmjs.com) │
│ └── Search for packages, read docs │
│ └── Manage your published packages │
│ └── View download stats and security advisories │
└─────────────────────────────────────────────────────────────────┘
What is a "package"?
A package is a reusable bundle of code that solves a specific problem. Instead of writing everything from scratch, you install packages that other developers have already built and tested.
// WITHOUT a package — writing your own date formatter
function formatDate(date) {
const months = ['Jan', 'Feb', 'Mar', /* ... 9 more */];
const d = new Date(date);
return `${months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
// What about timezones? Locales? Relative time? Edge cases?
// This quickly becomes 100+ lines of code with bugs.
}
// WITH a package — one line
const dayjs = require('dayjs');
console.log(dayjs().format('MMM D, YYYY')); // Apr 11, 2026
console.log(dayjs('2009-05-27').fromNow()); // 17 years ago
2. npm vs yarn vs pnpm
| Feature | npm | yarn (Classic/Berry) | pnpm |
|---|---|---|---|
| Created by | Isaac Z. Schlueter (2010) | Facebook (2016) | Zoltan Kochan (2017) |
| Comes with Node.js | Yes | No (install separately) | No (install separately) |
| Speed | Good (improved greatly) | Fast (parallel installs) | Fastest (hard links, symlinks) |
| Disk usage | One copy per project | One copy per project | Shared store (saves disk space) |
| Lock file | package-lock.json | yarn.lock | pnpm-lock.yaml |
| Workspaces | Yes (npm 7+) | Yes | Yes |
| Security | npm audit | yarn audit | pnpm audit |
| Market share | Dominant (default) | Significant | Growing fast |
Which should you use?
Starting out? → Use npm (it's already installed)
Working on a team? → Use whatever the team uses (check the lock file)
Performance matters? → Consider pnpm (fastest, most disk-efficient)
For this course, we use npm. The concepts (install, dependencies, scripts) are the same across all three.
3. Initializing a Project — npm init
Every Node.js project needs a package.json file. It is the identity card of your project.
Interactive initialization
npm init
# It will ask you questions:
# package name: (my-project)
# version: (1.0.0)
# description: A sample Node.js project
# entry point: (index.js)
# test command: jest
# git repository:
# keywords: node, learning
# author: Arjun
# license: (ISC) MIT
#
# Creates package.json with your answers
Quick initialization (skip questions)
npm init -y
# Creates package.json with ALL defaults — no questions asked
{
"name": "my-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Setting defaults for npm init
# Set your defaults once — used every time you run npm init
npm config set init-author-name "Arjun Sharma"
npm config set init-author-email "arjun@example.com"
npm config set init-license "MIT"
npm config set init-version "0.1.0"
4. Installing Packages
Installing a production dependency
# Full syntax
npm install express
# Shorthand
npm i express
# Install a specific version
npm i express@4.18.2
# Install multiple packages at once
npm i express mongoose dotenv
What happens when you run npm install express:
1. npm queries the registry (registry.npmjs.org) for "express"
2. Downloads express and ALL its sub-dependencies
3. Places everything in ./node_modules/
4. Adds "express" to "dependencies" in package.json
5. Records exact versions in package-lock.json
Install flags
# Production dependency (default — goes into "dependencies")
npm install express
npm i express
npm i express --save # Explicit (same as default since npm 5)
# Development dependency (goes into "devDependencies")
npm install nodemon --save-dev
npm i nodemon -D # Shorthand for --save-dev
# Global installation (available system-wide, not project-specific)
npm install -g nodemon
npm i -g typescript
# Install exact version (no ^ or ~ in package.json)
npm install express --save-exact
npm i express -E
# Install from a Git repository
npm install git+https://github.com/user/repo.git
# Install all dependencies from package.json (in a cloned project)
npm install
npm i
Install flags summary
| Flag | Short | Where It Saves | Use Case |
|---|---|---|---|
(none) / --save | dependencies | Packages needed at runtime (express, mongoose) | |
--save-dev | -D | devDependencies | Packages for development only (nodemon, jest, eslint) |
--global | -g | System-wide | CLI tools you want everywhere (typescript, npm-check-updates) |
--save-exact | -E | Exact version | When you need a pinned version |
--no-save | Not saved | One-off install, not added to package.json |
5. Dependencies vs devDependencies
This distinction matters in production deployments.
{
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.6.3",
"dotenv": "^16.3.1",
"bcrypt": "^5.1.1",
"jsonwebtoken": "^9.0.2"
},
"devDependencies": {
"nodemon": "^3.0.2",
"jest": "^29.7.0",
"eslint": "^8.56.0",
"prettier": "^3.1.1",
"supertest": "^6.3.3"
}
}
When to use which
| Type | When to Use | Examples |
|---|---|---|
| dependencies | Code that runs in production — your app needs it to work | express, mongoose, bcrypt, jsonwebtoken, cors |
| devDependencies | Code only needed during development/testing/building — NOT in production | nodemon, jest, eslint, prettier, typescript, webpack |
Why it matters
# In production, you can skip devDependencies to save space/time:
npm install --production
# OR
NODE_ENV=production npm install
# This skips nodemon, jest, eslint, etc. — only installs what the app needs
# Result: smaller node_modules, faster deploys, less attack surface
Quick decision guide
Ask yourself: "Does my running application need this package to work?"
YES → dependencies
express (handles HTTP requests)
mongoose (talks to database)
bcrypt (hashes passwords at runtime)
NO → devDependencies
nodemon (restarts server during dev — not needed in production)
jest (runs tests — tests don't run in production)
eslint (checks code style — already checked before deploy)
6. The node_modules Folder
What is it?
When you run npm install, all packages (and their sub-dependencies) are downloaded into the node_modules directory.
my-project/
├── node_modules/ ← All installed packages live here
│ ├── express/ ← The express package
│ │ ├── lib/
│ │ ├── index.js
│ │ └── package.json
│ ├── accepts/ ← A dependency OF express
│ ├── body-parser/ ← Another dependency of express
│ ├── cookie/
│ ├── ... (30+ packages just for express)
│ └── .package-lock.json
├── package.json
└── package-lock.json
Why is node_modules so large?
# Install express (one package)
npm i express
# Check how many packages were installed
ls node_modules | wc -l
# ~65 packages!
# Check the size
du -sh node_modules
# ~8 MB for just express
# A real project can easily have 200-500 MB in node_modules
Express depends on other packages, which depend on other packages, which depend on other packages. This is the dependency tree.
The golden rule: NEVER commit node_modules
┌─────────────────────────────────────────────────────────────────┐
│ CRITICAL RULE │
│ │
│ NEVER commit node_modules to Git. │
│ │
│ Why: │
│ • It's huge (100s of MB — bloats your repository) │
│ • It's reproducible (npm install recreates it from lock file) │
│ • It contains platform-specific binaries (won't work cross-OS)│
│ • It changes constantly (noise in git history) │
│ │
│ Instead: Commit package.json + package-lock.json │
│ Other developers run: npm install │
└─────────────────────────────────────────────────────────────────┘
.gitignore setup
# Create .gitignore with essential entries
echo "# Dependencies
node_modules/
# Environment variables
.env
.env.local
.env.*.local
# OS files
.DS_Store
Thumbs.db
# Build output
dist/
build/
# Test coverage
coverage/
# Editor directories
.vscode/
.idea/" > .gitignore
Recreating node_modules
# Scenario: You clone a project from GitHub
git clone https://github.com/user/project.git
cd project
# node_modules doesn't exist yet — that's expected!
npm install
# npm reads package.json and package-lock.json
# Downloads and installs everything into node_modules/
# Now you're ready to go
7. npx — Run Without Installing
npx comes bundled with npm (5.2+). It runs packages without installing them globally.
Why npx exists
# WITHOUT npx — you had to install globally first
npm install -g create-react-app
create-react-app my-app
# Problem: The global package gets outdated; takes up space
# WITH npx — always uses the latest version, no global install
npx create-react-app my-app
# Downloads temporarily, runs, then cleans up
Common npx use cases
# Create a React app
npx create-react-app my-app
# Create a Next.js app
npx create-next-app@latest my-app
# Create an Express app with generator
npx express-generator my-api
# Run a locally installed package
npx nodemon server.js
# (Finds nodemon in ./node_modules/.bin/ and runs it)
# Initialize ESLint config
npx eslint --init
# Run a specific version of a package
npx node@18 -e "console.log(process.version)"
# Execute a GitHub gist
npx https://gist.github.com/user/abc123
npx vs npm
| Scenario | npm | npx |
|---|---|---|
| Install a package permanently | npm i -g typescript | N/A |
| Run a package once without installing | N/A | npx typescript --version |
| Run a locally installed tool | ./node_modules/.bin/jest | npx jest |
| Use the latest version of a generator | Install, then run | npx create-next-app@latest |
8. Semantic Versioning (SemVer)
Every npm package uses semantic versioning — a three-number system that communicates what changed.
The format: MAJOR.MINOR.PATCH
4 . 18 . 2
│ │ │
│ │ └── PATCH: Bug fixes, no new features
│ │ (safe to update — nothing breaks)
│ │
│ └── MINOR: New features, backward-compatible
│ (safe to update — old code still works)
│
└── MAJOR: Breaking changes
(DANGEROUS to update — old code may break!)
Real-world examples
express 4.18.2 → 4.18.3 PATCH: Fixed a security bug
express 4.18.2 → 4.19.0 MINOR: Added new middleware feature
express 4.18.2 → 5.0.0 MAJOR: Changed API — your code might break!
Version ranges in package.json
| Symbol | Meaning | Example | Matches |
|---|---|---|---|
^ (caret) | Compatible with version — allows MINOR + PATCH updates | ^4.18.2 | >=4.18.2 and <5.0.0 |
~ (tilde) | Approximately — allows only PATCH updates | ~4.18.2 | >=4.18.2 and <4.19.0 |
| (none) | Exact version — no updates | 4.18.2 | Only 4.18.2 |
* | Any version | * | Latest |
>= | Greater than or equal | >=4.0.0 | 4.0.0 and above |
4.x | Any minor/patch in major 4 | 4.x | >=4.0.0 and <5.0.0 |
The most common: ^ (caret)
{
"dependencies": {
"express": "^4.18.2"
}
}
This means: "Install 4.18.2 or any newer version that's less than 5.0.0."
^4.18.2 allows: 4.18.3, 4.18.4, 4.19.0, 4.20.0, 4.99.99
^4.18.2 blocks: 5.0.0, 3.x.x
Why ^ is the default
- MINOR and PATCH updates are supposed to be backward-compatible
- You get bug fixes and new features automatically
- You are protected from breaking changes (major version bump)
9. Managing Packages — Update, Remove, Audit
Removing packages
# Remove from dependencies
npm uninstall express
npm un express # Shorthand
# Remove from devDependencies
npm uninstall nodemon -D
# Remove a global package
npm uninstall -g typescript
Updating packages
# Check which packages are outdated
npm outdated
# Package Current Wanted Latest Location
# express 4.18.2 4.19.1 4.19.1 my-project
# mongoose 7.6.3 7.8.0 8.1.0 my-project
# ↑
# Latest may be a MAJOR update (dangerous)
# Update all packages (respects semver ranges in package.json)
npm update
# Update a specific package
npm update express
# Update to the absolute latest (including major — be careful!)
npm install express@latest
# Interactive update tool (third-party, very useful)
npx npm-check-updates -i
Listing installed packages
# List top-level dependencies
npm ls --depth=0
# my-project@1.0.0
# ├── express@4.18.2
# ├── mongoose@7.6.3
# └── nodemon@3.0.2
# List all dependencies (including sub-dependencies)
npm ls
# List globally installed packages
npm ls -g --depth=0
10. npm Scripts
npm scripts let you define shortcuts for common commands in your package.json.
Defining scripts
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "jest",
"test:watch": "jest --watch",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"format": "prettier --write src/",
"build": "tsc",
"clean": "rm -rf dist node_modules",
"seed": "node scripts/seed-database.js",
"migrate": "node scripts/migrate.js"
}
}
Running scripts
# Special scripts (don't need "run")
npm start # Runs "start" script
npm test # Runs "test" script
npm stop # Runs "stop" script
npm restart # Runs "restart" (or stop + start)
# Custom scripts (need "run")
npm run dev # Runs "dev" script
npm run lint # Runs "lint" script
npm run build # Runs "build" script
npm run seed # Runs "seed" script
# List all available scripts
npm run
# Lifecycle scripts included in my-project:
# start: node server.js
# test: jest
# available via `npm run-script`:
# dev: nodemon server.js
# lint: eslint src/
# ...
Pre and post hooks
npm automatically runs pre and post scripts if they exist:
{
"scripts": {
"prebuild": "echo 'Cleaning old build...' && rm -rf dist",
"build": "tsc",
"postbuild": "echo 'Build complete!'",
"pretest": "npm run lint",
"test": "jest",
"posttest": "echo 'All tests passed!'"
}
}
npm run build
# Runs in order:
# 1. prebuild → Cleaning old build...
# 2. build → tsc (TypeScript compiler)
# 3. postbuild → Build complete!
Common script patterns
| Script | Command | Purpose |
|---|---|---|
start | node server.js | Start the production server |
dev | nodemon server.js or node --watch server.js | Start with auto-restart |
test | jest or mocha | Run test suite |
test:watch | jest --watch | Run tests on file change |
lint | eslint . | Check code for errors |
lint:fix | eslint . --fix | Auto-fix linting errors |
format | prettier --write . | Format all code |
build | tsc or webpack | Compile/bundle for production |
clean | rm -rf dist node_modules | Remove generated files |
seed | node scripts/seed.js | Populate database with test data |
Passing arguments to scripts
# Pass arguments after --
npm run test -- --verbose --coverage
# Equivalent to: jest --verbose --coverage
npm run dev -- --port 4000
# Equivalent to: nodemon server.js --port 4000
11. Security — npm audit
Why security matters
When you install a package, you are running someone else's code on your machine. That code can have vulnerabilities.
Running an audit
npm audit
# ┌───────────────┬──────────────────────────────────────────────────────┐
# │ Severity │ high │
# ├───────────────┼──────────────────────────────────────────────────────┤
# │ Package │ express │
# ├───────────────┼──────────────────────────────────────────────────────┤
# │ Dependency of │ express │
# ├───────────────┼──────────────────────────────────────────────────────┤
# │ Path │ express > qs │
# ├───────────────┼──────────────────────────────────────────────────────┤
# │ More info │ https://npmjs.com/advisories/1234 │
# └───────────────┴──────────────────────────────────────────────────────┘
# found 2 vulnerabilities (1 moderate, 1 high)
# Auto-fix vulnerabilities (safe — only updates within semver range)
npm audit fix
# Force fix (may include breaking changes — use carefully)
npm audit fix --force
# See what would change without actually doing it
npm audit fix --dry-run
# Get a detailed JSON report
npm audit --json
Security best practices
| Practice | Why |
|---|---|
Run npm audit regularly | Catches known vulnerabilities early |
| Keep dependencies updated | Older packages have more known vulnerabilities |
Use package-lock.json | Locks exact versions — prevents unexpected updates |
| Review before installing | Check download counts, maintenance status, last update on npmjs.com |
| Avoid unused packages | Every dependency is an attack surface — remove what you don't use |
Use npx instead of -g | Global packages are harder to track and update |
Check npm ls for duplicates | Duplicate packages increase risk and bundle size |
Key Takeaways
- npm is three things: a registry (2M+ packages), a CLI tool (comes with Node.js), and a website (npmjs.com).
npm init -ycreates a package.json instantly — the starting point of every Node.js project.npm install <package>downloads a package and all its sub-dependencies intonode_modules/.dependenciesare for runtime code (express, mongoose);devDependenciesare for development-only tools (nodemon, jest).- Never commit
node_modules— add it to.gitignoreand letnpm installrecreate it from the lock file. - npx runs packages without global installation — always up to date, no cleanup needed.
- Semantic versioning (
^4.18.2) uses MAJOR.MINOR.PATCH — the^allows safe minor and patch updates. - npm scripts in package.json are the standard way to define project commands —
npm start,npm test,npm run dev. npm auditchecks for known security vulnerabilities — run it regularly and fix issues promptly.
Explain-It Challenge
Can you explain to a friend: "What happens when you run
npm install express? Where does the code go, how does package.json change, and why should you never commit node_modules?" If you can do it in under 90 seconds without notes, you have mastered this topic.
Navigation: ← 3.1 Overview · Next → 3.1.e Package JSON Deep Dive