Chapter 19: Node.js Managing Dependencies
1. What does “managing dependencies” actually mean?
In a Node.js project you usually have two kinds of external code you depend on:
| Type | Purpose | Installed where? | Example packages | Lives in production build? |
|---|---|---|---|---|
| dependencies | Libraries your app needs to run | npm install <pkg> | express, zod, prisma, jsonwebtoken, cors | Yes |
| devDependencies | Tools only needed during development / build / test | npm install –save-dev <pkg> | typescript, eslint, vitest, nodemon, tsup | No |
Managing dependencies means:
- Knowing which packages to install
- Knowing where to install them (dependencies vs devDependencies)
- Keeping versions consistent across machines / team members / CI
- Knowing how to add, update, remove, audit, and understand what’s inside node_modules
- Understanding the lockfile (package-lock.json) and why it’s sacred
2. The most important files involved
| File | Who creates it | Purpose | Should you commit it to git? |
|---|---|---|---|
| package.json | You (npm init) | Declares project metadata + wanted versions of packages | Yes |
| package-lock.json | npm (automatically) | Locks exact resolved versions of every dependency (including sub-dependencies) | Yes – very important! |
| node_modules/ | npm install | Contains all the actual downloaded code | No – add to .gitignore |
Golden rule 2026:
Always commit package.json and package-lock.json Never commit node_modules
3. Core commands – how you actually manage dependencies every day
| What you want to do | Command | What really happens |
|---|---|---|
| Start a new project | npm init -y | Creates basic package.json |
| Add a runtime dependency | npm install express | Installs latest express + saves “express”: “^4.19.2” in dependencies |
| Add a dev-only tool | npm install –save-dev nodemon or npm i -D nodemon | Saves in devDependencies |
| Install everything listed in package.json | npm install or just npm i | Reads package.json + package-lock.json → installs exact versions |
| Install exact versions from lockfile (CI) | npm ci | Deletes node_modules first → installs exactly what’s in package-lock.json |
| Update packages to latest compatible versions | npm update | Respects semver ranges (^, ~) – updates package-lock.json |
| See which packages are outdated | npm outdated | Shows current vs wanted vs latest |
| Remove a package | npm uninstall lodash | Removes from node_modules + package.json |
| Check for security vulnerabilities | npm audit | Scans all dependencies |
| Fix simple security issues automatically | npm audit fix or npm audit fix –force | Updates vulnerable packages within semver ranges |
4. Real example – building a small project step by step
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# 1. Create project mkdir task-api cd task-api # 2. Initialize npm init -y # 3. Add runtime dependencies npm install express zod dotenv cors helmet # 4. Add development dependencies npm install -D typescript @types/node @types/express tsx nodemon \ eslint prettier eslint-config-standard-with-typescript \ vitest @vitest/coverage-v8 # 5. Add package.json type for ESM (very important in 2026) # Also add useful scripts |
Your package.json might now look like this:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
{ "name": "task-api", "version": "1.0.0", "private": true, "type": "module", "main": "src/index.ts", "scripts": { "dev": "tsx watch src/index.ts", "start": "node dist/index.js", "build": "tsc && tsup", "lint": "eslint . --ext .ts", "lint:fix": "eslint . --ext .ts --fix", "format": "prettier --write .", "test": "vitest run", "test:watch": "vitest", "typecheck": "tsc --noEmit" }, "dependencies": { "express": "^4.19.2", "zod": "^3.23.8", "dotenv": "^16.4.5", "cors": "^2.8.5", "helmet": "^7.1.0" }, "devDependencies": { "typescript": "^5.5.4", "@types/node": "^20.14.10", "@types/express": "^4.17.21", "tsx": "^4.19.0", "nodemon": "^3.1.7", "eslint": "^9.9.0", "prettier": "^3.3.3", "eslint-config-standard-with-typescript": "^43.0.1", "vitest": "^2.0.5", "@vitest/coverage-v8": "^2.0.5" }, "engines": { "node": ">=20.0.0" } } |
5. Understanding version ranges (semver) – very important
When you see this in package.json:
|
0 1 2 3 4 5 6 7 8 |
"express": "^4.19.2", "zod": "~3.23.8", "lodash": "4.17.21" |
| Symbol | Meaning | Allows updates to | Common usage |
|---|---|---|---|
| ^ | Compatible with | Same major version, any minor + patch | Most common for libraries |
| ~ | Patch updates only | Same major + minor, any patch | When you want stricter control |
| (none) | Exact version | Only this exact version | When you need absolute stability |
| >= | Greater than or equal | Any version >= this one | Rare – usually for engines field |
Most common choices 2026:
- ^ for most libraries (express, zod, cors…)
- ~ when you’re very cautious about minor version changes
- Exact version (“4.17.21”) only when you have a very good reason
6. Best practices & tips used by experienced developers
- Always commit package-lock.json (prevents “works on my machine”)
- Use npm ci in CI/CD pipelines, Dockerfiles, GitHub Actions
- Run npm audit regularly (at least weekly)
- Prefer npm audit fix over manually updating vulnerable packages
- Use npm outdated before doing big updates
- Consider pinning major versions in production after testing
- Use volta / nvm / corepack to lock Node & npm versions across team
- Keep devDependencies separate — they don’t go to production
- Use private: true in package.json for applications
Summary – Quick daily workflow cheat sheet
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# New project npm init -y npm install express zod dotenv npm install -D typescript tsx nodemon eslint prettier vitest # Daily development npm install # after git pull npm run dev # Before commit / push npm run lint npm run format npm run test npm run typecheck # Security & maintenance npm audit npm outdated npm update npm audit fix |
Which part would you like to explore much deeper next?
- How package-lock.json really works (resolution algorithm)
- Semantic versioning in practice (with real examples)
- Difference between npm, yarn, pnpm, bun in 2026
- Managing peerDependencies and optionalDependencies
- Strategies for updating dependencies safely in large projects
- How to deal with conflicting / duplicate dependencies
Just tell me which topic feels most useful right now — I’ll continue with detailed examples and explanations. 😊
