Quality over quantity. Every dependency adds:
- Bundle size (slower downloads)
- Security risk (more attack surface)
- Maintenance burden (updates, breaking changes)
- Complexity (more to understand)
Only add dependencies that provide significant value.
Before adding a dependency, ask:
┌────────────────────────────────────────┐
│ Can I build this myself in <1 hour? │
└───────────┬────────────────────────────┘
│
├─► Yes ─► Build it yourself
│
└─► No ─► Continue ↓
┌────────────────────────────────────────┐
│ Is this a core feature of the app? │
└───────────┬────────────────────────────┘
│
├─► Yes ─► Build it yourself
│ (to maintain control)
│
└─► No ─► Continue ↓
┌────────────────────────────────────────┐
│ Is there a well-maintained library? │
└───────────┬────────────────────────────┘
│
├─► Yes ─► Check criteria ↓
│
└─► No ─► Build it yourself
┌────────────────────────────────────────┐
│ Library Quality Checklist: │
│ - Active maintenance (updated <6mo) │
│ - Good documentation │
│ - High test coverage │
│ - Reasonable bundle size │
│ - Few dependencies itself │
│ - TypeScript support │
│ - No known security issues │
└───────────┬────────────────────────────┘
│
├─► All pass ─► Use the library
│
└─► Some fail ─► Consider building it
TypeScript
npm install -D typescript @types/nodeWhy: Type safety catches errors before runtime, great for learning
React (for frontends)
npm install react react-dom
npm install -D @types/react @types/react-domWhy: Industry standard, huge ecosystem, transferable skills
Tailwind CSS (for styling)
npm install -D tailwindcss postcss autoprefixerWhy: Utility-first, no naming conflicts, faster than writing custom CSS
Vite (for build tooling)
npm create vite@latestWhy: Fast HMR, modern, better than Create React App
Express (API framework)
npm install express
npm install -D @types/expressWhy: Simple, well-documented, huge community
Fastify (faster alternative to Express)
npm install fastifyWhy: Better performance, modern, excellent TypeScript support
Prisma (database ORM)
npm install prisma @prisma/client
npm install -D prismaWhy: Type-safe queries, automatic migrations, great DX
Zod (validation)
npm install zodWhy: TypeScript-first, runtime validation, composable schemas
bcrypt (password hashing)
npm install bcrypt
npm install -D @types/bcryptWhy: Industry standard, secure, simple API
jsonwebtoken (JWT tokens)
npm install jsonwebtoken
npm install -D @types/jsonwebtokenWhy: Standard auth solution, widely used
helmet (security headers)
npm install helmetWhy: Sets security headers automatically, prevents common attacks
express-rate-limit (rate limiting)
npm install express-rate-limitWhy: Prevents brute force, simple to configure
cors (CORS handling)
npm install cors
npm install -D @types/corsWhy: Handles CORS properly, configurable
ESLint (linting)
npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-pluginWhy: Catches errors, enforces style, teaches best practices
Prettier (formatting)
npm install -D prettierWhy: Consistent formatting, no debates, auto-fix
Vitest (testing)
npm install -D vitestWhy: Fast, modern, great TypeScript support
date-fns (date manipulation)
npm install date-fnsWhy: Lightweight, tree-shakeable (only import what you need) Alternative: Native Date API is often sufficient
lodash-es (utilities)
npm install lodash-es
npm install -D @types/lodash-esWhy: Tree-shakeable, well-tested utilities
axios (HTTP client)
npm install axiosWhy: Better error handling than fetch, interceptors, timeouts Alternative: Native fetch is often sufficient
Don't use: Outdated, huge bundle size, mutable API Use instead: date-fns or native Intl API
// ❌ Bad - moment.js (huge bundle)
import moment from 'moment';
const date = moment().format('YYYY-MM-DD');
// ✅ Good - date-fns (lightweight)
import { format } from 'date-fns';
const date = format(new Date(), 'yyyy-MM-dd');
// ✅ Best - native (no dependency)
const date = new Date().toISOString().split('T')[0];Don't use: Import whole library Use instead: Import specific functions
// ❌ Bad - imports entire lodash
import _ from 'lodash';
const unique = _.uniq(array);
// ✅ Good - imports only what's needed
import { uniq } from 'lodash-es';
const unique = uniq(array);
// ✅ Best - native (no dependency)
const unique = [...new Set(array)];Don't use: Outdated, React handles DOM Use instead: React state and refs
// ❌ Bad - jQuery with React
$('#button').on('click', () => { ... });
// ✅ Good - React way
<button onClick={() => { ... }}>Click</button>Don't use: When crypto.randomUUID() works Use instead: Native crypto API (Node 16.7+, all modern browsers)
// ❌ Unnecessary dependency
import { v4 as uuidv4 } from 'uuid';
const id = uuidv4();
// ✅ Native (no dependency)
const id = crypto.randomUUID();{
"dependencies": {
"express": "4.18.2", // ✅ Locked version
"react": "^18.2.0" // ❌ May update (breaking changes)
}
}Prefer exact versions to prevent unexpected breaking changes:
npm install --save-exact express
# Or set in .npmrc
echo "save-exact=true" > .npmrc# Check for outdated packages
npm outdated
# Check for security vulnerabilities
npm audit
# Fix auto-fixable vulnerabilities
npm audit fix
# Update packages (carefully)
npm update
# Interactive update with version selection
npx npm-check-updates -i# Must pass before merging
npm audit --audit-level=high
# If vulnerabilities found:
# 1. Check if there's a fix available
npm audit fix
# 2. If no fix, check if it affects your code
npm audit
# 3. Document why it's acceptable (if it is)
# 4. Create issue to revisit laterUse bundlephobia.com to check package sizes before installing:
# Example: Check axios size
# Visit: https://bundlephobia.com/package/axiosOr use npm:
npx bundle-phobia axiosUse workspaces to share common dependencies:
// Root package.json
{
"workspaces": [
"packages/*"
],
"devDependencies": {
"typescript": "^5.0.0", // Shared across all packages
"prettier": "^3.0.0",
"eslint": "^8.0.0"
}
}Only install where needed:
# Install only in specific package
pnpm add express --filter backend
pnpm add react --filter frontend# Check package details
npm view express
# Check recent activity
npm view express time
# Check maintainers
npm view express maintainers
# Check dependencies
npm view express dependencies# ❌ Typo - might be malicious
npm install expres
# ✅ Correct
npm install express
# ❌ Suspicious - random suffix
npm install react-helper-utils-v2
# ✅ Official packages usually have clean names
npm install reactVisit npmjs.com and check:
- Popularity
- Quality (tests, README, etc.)
- Maintenance (last update, issue response)
# Weekly: Check for vulnerabilities
npm audit
# Before deploy: Ensure no high/critical
npm audit --audit-level=highSigns you might have too many:
node_modulesis >500MBnpm installtakes >2 minutes- Bundle size >500KB (before gzip)
- Don't know what half the dependencies do
Audit process:
# 1. List all dependencies
npm list --depth=0
# 2. For each, ask: "Do we actually use this?"
npm ls <package-name>
# 3. Remove unused
npm uninstall <package-name>
# 4. Check what depends on what
npm ls
# 5. Look for opportunities to use native features
# Example: Replace axios with fetch
# Example: Replace lodash with native methodsWhen adding dependencies:
- Check if native alternative exists - Use built-in features first
- Explain why the dependency is needed - Document the decision
- Comment on the choice - What it does, why this library
- Check bundle size - Use bundlephobia.com
- Verify maintenance - Last updated <6 months ago
- Lock the version - Use exact versions, not ranges
- Document in README - List major dependencies and why
/**
* DEPENDENCY ADDED: zod
*
* What: Runtime type validation library
* Why: Need to validate API requests and ensure type safety at runtime
* Alternatives considered:
* - Joi: Less TypeScript-friendly
* - Yup: Older, less modern API
* - Manual validation: Too error-prone, lots of boilerplate
*
* Bundle size: 13.5KB (minified + gzipped)
* Last updated: 2 weeks ago
* Security: No known vulnerabilities
*/
import { z } from 'zod';Before adding any dependency:
- Checked if native alternative exists
- Verified it's actively maintained (<6 months since update)
- Checked bundle size (<50KB preferred, <100KB acceptable)
- No known security vulnerabilities
- Good TypeScript support (has @types or built-in types)
- Documented why this dependency is needed
- Added comment explaining the choice
- Locked to specific version
- Ran
npm auditafter installing
- bundlephobia.com - Check bundle sizes
- npm trends - Compare package popularity
- Snyk Advisor - Package health scores
- Can I Use - Browser support for native features
- You might not need jQuery
- You might not need Lodash