Essential Libraries Basics
10 examples for how we pick, pin, and sunset npm dependencies on Node.js 24 TypeScript backends - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples for how we pick, pin, and sunset npm dependencies on Node.js 24 TypeScript backends - 7 basic and 3 intermediate.
Start new services with this curated set before evaluating alternatives.
| Concern | Default | When to reconsider |
|---|---|---|
| Validation | zod | JSON Schema-only Fastify routes with no shared types |
| Logging | pino | Cloud provider mandates another format (rare) |
| HTTP client | got or axios | Simple outbound calls with built-in fetch |
| Dates/timezones | Luxon | Pure date math without IANA zones |
| Queue + cache | BullMQ + ioredis | SQS-only AWS shops with no Redis |
CONTRIBUTING.md so PRs do not re-debate basics weekly.Related: Essential Libraries Best Practices - selection checklist
Score candidates before npm install.
## Dependency evaluation: <package-name>
- [ ] Last publish < 12 months OR explicit LTS maintainer
- [ ] Open issues responded within ~2 weeks for security reports
- [ ] Weekly downloads stable or growing (not abandoned spike)
- [ ] TypeScript types built-in or DefinitelyTyped quality checked
- [ ] No install scripts unless reviewed (supply chain risk)
- [ ] Transitive count acceptable (npm ls <pkg> --all)
- [ ] License compatible (MIT/Apache-2.0 typical)npm audit after install; block high severity without exception ticket.package.json expresses intent; the lockfile expresses reality.
{
"dependencies": {
"zod": "^3.24.0",
"pino": "^9.0.0",
"ioredis": "^5.4.0"
},
"engines": {
"node": "24.18.0"
}
}npm install # updates lockfile locally with intent
npm ci # CI installs exact lockfile - never driftRelated: Lockfiles & Reproducible Installs
Node 24 reduces third-party surface for common tasks.
// Built-in fetch - no got/axios required for simple GET
const res = await fetch("https://api.example.com/health");
const body = await res.json();
// Built-in test runner - no jest required for small services
import { test } from "node:test";
import assert from "node:assert/strict";
test("health shape", () => {
assert.equal(typeof body.status, "string");
});node:test.npm ci.Related: Built-in APIs Basics
Duplicate date libraries or HTTP clients create inconsistent behavior and bloated images.
# Audit overlap before approving a PR
npm ls moment date-fns luxon dayjs 2>/dev/null
npm ls axios got undici node-fetch 2>/dev/null| Bad overlap | Fix |
|---|---|
moment + luxon | Pick Luxon for IANA zones; remove moment |
axios + got | Standardize per service; shared internal SDK if needed |
winston + pino | Pino only for JSON services |
Tie unusual dependencies to a decision record.
{
"dependencies": {
"bullmq": "^5.0.0",
"ioredis": "^5.4.0"
}
}<!-- docs/adr/003-job-queue.md -->
# ADR 003: BullMQ for async jobs
- Status: accepted
- Context: Need delayed jobs, retries, DLQ with Redis already in stack
- Decision: BullMQ over raw Redis lists
- Consequences: Operate Redis HA; monitor queue depthdocs/adr/ and are linked from PR descriptions.Treat new dependencies as production risk, not convenience.
npm audit --audit-level=high
npx socket npm audit # optional: proactive supply chain scan# .github/workflows/pr-checks.yml (excerpt)
- run: npm ci
- run: npm audit --audit-level=highpostinstall scripts in lockfile diffs - they run on every developer laptop.Related: Supply Chain: npm audit & Socket
Remove dependencies with a migration plan, not a big-bang delete.
## Sunset plan: winston → pino
Week 1: ADR accepted; no new winston imports (lint rule)
Week 2-3: Migrate high-traffic services; dual-log if needed
Week 4: Remove winston from package.json; npm audit confirms gone// Transitional adapter - delete after migration
import pino from "pino";
export const log = pino({ level: process.env.LOG_LEVEL ?? "info" });#backend with affected services list.Shared libraries force consistent transitive versions.
{
"name": "@acme/shared-validation",
"dependencies": {
"zod": "^3.24.0"
},
"peerDependencies": {
"zod": "^3.24.0"
}
}@acme/shared-validation.zod across all package.json files weekly.npm run typecheck at repo root after grouped upgrades.Related: Workspaces & Monorepos
Decline packages that fail the team bar even if they are popular on npm.
| Reject when | Example | Alternative |
|---|---|---|
| Unmaintained 2+ years | abandoned ORM wrapper | Prisma, Drizzle, raw pg |
| No TypeScript path | untyped mega-lib | thinner typed alternative |
| Heavy native addon for one function | image lib for resize | platform service or sharp with ADR |
| Duplicates platform capability | dotenv in k8s-only prod | platform inject + Zod at boot |
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 16, 2026