Essential Libraries Best Practices
Rules that keep npm dependencies maintainable, secure, and consistent across Node.js services.
How to Use This List
- Apply when bootstrapping a new API or worker service.
- Revisit during quarterly dependency hygiene sprints.
- Enforce via CI audit gates and PR template checkboxes.
- Check items when onboarding a library or sunsetting a legacy one.
A - Selection & Standards
- Document the default essential stack in CONTRIBUTING.md. zod, pino, got or axios, Luxon, BullMQ + ioredis unless ADR says otherwise.
- Evaluate new packages with a written checklist. Maintenance, security response, license, transitive weight.
- Require ADR link for non-default libraries. One paragraph minimum in
docs/adr/. - Prefer Node 24 built-ins first.
fetch,node:test,node:cryptobefore npm equivalents. - One library per concern per service. No moment + Luxon + date-fns overlap.
- Reject packages with unmitigated high audit findings. Exception ticket with expiry date.
B - Pinning & Upgrades
- Commit lockfile on every dependency change.
package-lock.jsonor pnpm/yarn equivalent. - Use
npm ciin CI, nevernpm install. Reproducible graphs on every pipeline run. - Pin Node in engines to Active LTS.
24.18.0aligned with.nvmrcand Docker base image. - Group Renovate/Dependabot PRs weekly. Smaller blast radius than monthly mega-bumps.
- Run typecheck and full test suite on lockfile-only PRs. Transitive breaks happen without direct dep edits.
- Align shared workspace package versions.
@acme/shared-*peer deps match service deps.
C - Security & Supply Chain
- Run
npm audit --audit-level=highon every PR. Block merge until fixed or excepted. - Review install scripts in lockfile diffs.
postinstalladditions are high risk. - Enable provenance for published internal packages. Consumers verify CI-built artifacts.
- Scan with Socket or similar for proactive risk. Beyond CVE-only audit.
- No secrets in dependencies or
.envcommitted. Use platform inject and secrets managers. - Minimize native addon count. Each addon is a build and supply-chain surface.
D - Operations per Library
- Zod at boundaries only. HTTP, env, queue payloads - not every internal function.
- Pino JSON to stdout in production. No
pino-prettyin prod images; redact auth headers. - HTTP clients: timeouts and retry on all outbound calls. Idempotent retry only unless idempotency keys exist.
- Luxon or explicit UTC for scheduling. No
new Date("local string")without offset. - BullMQ workers as separate processes. Not inside HTTP server event loop.
- Redis key prefixes separate cache from queue keys.
cache:vs BullMQ internal prefix.
E - Sunset & Hygiene
- Sunset with migration window and lint ban on new imports. Track per-repo in a GitHub issue.
- Remove unused deps with
depcheckquarterly. Dead packages still audit-fail. - Document breaking upgrades in CHANGELOG. Especially major Zod, Fastify, BullMQ bumps.
- Re-evaluate ADRs annually. Maintainer churn or better built-ins may obsolete choices.
- Train reviewers on dependency PR red flags. Duplicate libs, missing lockfile, audit skip.
FAQs
How strict is the default stack?
Defaults are strong recommendations. Deviations need ADR, not hallway agreement. Exceptions expire when the ADR is revisited.
When is axios worth it over got?
When the team already standardized on axios interceptors across many services. Do not introduce both in one repo without migration plan.
Should internal packages wrap zod schemas?
Yes for cross-service contracts (@acme/contracts). Services import shared schemas; do not fork validation per API.
What triggers an emergency dependency removal?
Critical CVE with no patch, compromised package version, or malicious install script. Hotfix branch, lockfile bump, redeploy all affected services within SLA.
Related
- Essential Libraries Basics - pick, pin, sunset intro
- zod - validation at boundaries
- pino - production logging
- got / axios - outbound HTTP policies
- date-fns / Luxon - time zones
- bullmq / ioredis - queue + cache
- Package Managers Best Practices - lockfile policy
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.