nodejs-backend
>-
Works with
---
name: nodejs-backend
description: >-
license: MIT
---
# Node.js Backend
**Verify before implementing**: For framework-specific APIs (Express 5, Fastify 5, Node.js 22+ built-ins), look up current docs via Context7 (`query-docs`) before writing code. Training data may lag current releases.
## Framework Selection
| Context | Choose | Why |
|---------|--------|-----|
| Edge/Serverless | Hono | Zero-dep, fastest cold starts |
| Performance API | Fastify | Higher throughput than Express, built-in schema validation |
| Enterprise/team | NestJS | DI, decorators, structured conventions |
| Legacy/ecosystem | Express | Most middleware, widest adoption |
Ask user: deployment target, cold start needs, team experience, existing codebase.
## Architecture
```
src/
├── routes/ # HTTP: parse request, call service, format response
├── middleware/ # Auth, validation, rate limiting, logging
├── services/ # Business logic (no HTTP types)
├── repositories/ # Data access only (queries, ORM)
├── config/ # Env, DB pool, constants
└── types/ # Shared TypeScript interfaces
```
- Routes never contain business logic
- Services never import Request/Response
- Repositories never throw HTTP errors
- Dependencies point inward only (Clean Architecture rule): routes -> services -> repositories. Never the reverse.
- For scripts/prototypes: single file is fine -- ask "will this grow?"
## TypeScript Rules
- Use `import type { }` for type-only imports -- eliminates runtime overhead
- Prefer `interface` for object shapes (2-5x faster type resolution than intersections)
- Prefer `unknown` over `any` -- forces explicit narrowing
- Use `z.infer<typeof Schema>` as single source of truth -- never duplicate types and schemas
- Minimize `as` assertions -- use type guards instead
- Add explicit return types to exported functions (faster declaration emit)
- Untyped package? `declare module 'pkg' { const v: unknown; export default v; }` in `types/ambient.d.ts`
## Validation
**Zod** (TypeScript inference) or **TypeBox** (Fastify native). Validate at boundaries only: request entry, before DB ops, env vars at startup. Use `.extend()`, `.pick()`, `.omit()`, `.partial()`, `.merge()` for DRY schemas.
## Error Handling
Custom error hierarchy: `AppError(message, statusCode, isOperational)` → `ValidationError(400)`, `NotFoundError(404)`, `UnauthorizedError(401)`, `ForbiddenError(403)`, `ConflictError(409)`
Centralized handler middleware:
- `AppError` → return `{ error: message }` with statusCode
- Unknown → log full stack, return 500 + generic message in production
- Async wrapper: `const asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);`
Codes: 400 bad input | 401 no auth | 403 no permission | 404 missing | 409 conflict | 422 business rule | 429 rate limited | 500 server fault
## API Design
**Contract-first**: define route schemas (Zod schemas, Fastify JSON Schema, or OpenAPI spec) before writing handler logic. The schema is the contract -- implementation follows. Generate OpenAPI/Swagger docs from these schemas for interactive API documentation.
- **Hyrum's Law awareness**: every observable response field, ordering, or timing becomes a dependency for callers. Use Zod schemas or Fastify response schemas to control exactly what's serialized -- never return raw ORM objects or untyped objects from handlers.
- **Addition over modification**: add new optional fields rather than changing or removing existing ones. Removing a field from a response schema breaks callers silently. Deprecate first (mark in OpenAPI spec), remove in a later version.
- **Consistent error envelope**: all errors -- validation, auth, not-found, application -- must produce the same `{ error: { code, message, details? } }` structure. Centralize in the error handler middleware. Callers build error handling once; inconsistent errors force per-endpoint special cases.
- **Boundary validation**: validate at the middleware/route handler level (Zod `.parse()` on request body/params, Fastify schema validation). Services and repositories trust that input was validated at entry -- no redundant checks scattered through business logic.
- **Third-party responses are untrusted data**: validate shape and content of external API responses before using them in logic, rendering, or decision-making. A compromised or misbehaving service can return unexpected types, malicious content, or missing fields. Parse through a Zod schema before use.
- **Resources**: plural nouns (`/users`), max 2 nesting levels (`/users/:id/orders`)
- **Methods**: GET read | POST create | PUT replace | PATCH partial | DELETE remove
- **Versioning**: URL path `/api/v1/`
- **Response**: `{ data, pagination?: { page, limit, total, totalPages } }`
- **Queries**: `?page=1&limit=20&status=active&sort=createdAt,desc`
- Return `Location` header on 201. Use 204 for successful DELETE with no body.
## Async Patterns
| Pattern | Use When |
|---------|----------|
| `async/await` | Sequential operations |
| `Promise.all` | Parallel independent ops |
| `Promise.allSettled` | Parallel, some may fail |
| `Promise.race` | Timeout or first-wins |
Never use readFileSync or other sync methods in production -- use `fs.promises` or stream equivalents. Offload CPU work to worker threads (Piscina). Stream large payloads.
## Production Resilience
- **Fail-fast env validation**: parse and validate all environment variables at startup with a Zod schema (`const env = envSchema.parse(process.env)`). If invalid, crash before serving traffic. Never discover a missing env var on the first request that needs it.
- **Health endpoints**: expose both `/health` (shallow, always 200 if process is alive) and `/ready` (deep, verifies database, cache, and critical dependencies are reachable). Load balancers probe `/ready` for traffic routing; monitoring probes `/health` for process liveness. Don't conflate them.
- **Caching**: Redis cache-aside for DB/API responses; in-memory LRU with TTL for hot paths. Always invalidate on writes.
- **Load shedding**: `@fastify/under-pressure` (or equivalent) -- monitor event loop delay, heap, RSS; return 503 when thresholds exceeded.
- **Response schemas**: In Fastify, always define response schemas -- enables `fast-json-stringify` for 2-3x faster serialization.
- **Circuit breaker**: use `opossum` for outbound service calls. States: CLOSED (normal) -> OPEN (failing, return fallback) -> HALF_OPEN (probe). Prevents cascade failures when downstream services are down. When the outbound call *is* the security decision (authz check, trust score, license or entitlement gate), the fallback must be **deny**, and any fail-open allowance scopes to transport failure only -- connection refused, DNS failure, timeout. A response that arrived but cannot be trusted (4xx/5xx, malformed JSON, schema-invalid body, unknown verdict value) stays blocked: the endpoint was reached and did not answer. Absence of evidence is not evidence of trust. Same for "no history yet" states -- reject by default, allow only through an explicit onboarding opt-in.
## Observability
- **Define "working" before instrumenting**: write the questions an on-call engineer will ask when this is broken at 3am ("which dependency is timing out?", "is it all users or one tenant?"), then add only the telemetry that answers them. Instrumentation with no question behind it is cost and noise.
- **Pick the signal by the question it answers**: logs = "what happened in this one case?" (high-detail, structured, sampled under load); metrics = "how often / how fast / how saturated?" (cheap aggregates — keep label cardinality bounded, never user IDs or request IDs as labels); traces = "where did the time or the error go across services?".
- **Structured logging**: `pino` with a stable set of event names and a correlation/request ID propagated through async context (`AsyncLocalStorage`). Never `console.log` in production paths.
- **Metrics**: `prom-client` for RED per route — Rate (request count), Errors (error count), Duration (latency histogram). OpenTelemetry Node SDK for distributed traces across services.
- **Initialize tracing before app imports, then verify it fires**: the OTel SDK must start before the modules it instruments are required, or auto-instrumentation silently no-ops. Before relying on any signal, force an error and send test traffic in staging and confirm the log/metric/trace actually lands — untested instrumentation fails silent.
- **Alert on symptoms, not causes**: page on user-visible symptoms (error-rate spike, latency SLO burn, `/ready` flapping), not on causes (CPU high, heap growing). A cause with no symptom is a dashboard, not a page.
## Discipline
- Simplicity first -- every change as simple as possible, impact minimal code
- Only touch what's necessary -- avoid introducing unrelated changes
- No hacky workarounds -- if a fix feels wrong, step back and implement the clean solution
- Before adding a new abstraction, verify it appears in 3+ places. If not, inline it.
- If a fix requires bypassing TypeScript (`as any`, non-null assertions on untrusted data, `// @ts-ignore`), treat it as a design smell and find the typed solution
## Verify
- `tsc --noEmit` passes with zero errors
- `npm test` passes with zero failures
- No TypeScript bypasses (`as any`, `@ts-ignore`) in new code
## References
- [TypeScript config](./references/typescript-config.md) -- tsconfig, ESM, branded types, compiler performance
- [Security](./references/security.md) -- JWT, password hashing, rate limiting, OWASP
- [API design patterns](./references/api-design.md) -- pagination, filtering, sorting, deprecation, idempotency-key claim and retention
- [Database & production](./references/database-production.md) -- connection pooling, transactions, Docker, loggingMore Backend Frameworks skills
git-guardrails-claude-code
mattpocock/skills
Set up Claude Code hooks to block dangerous git commands (push, reset --hard, clean, branch -D, etc.) before they execute. Use when user wants to prevent destructive git operations, add git safety hooks, or block git push/reset in Claude Code.
azure-compute
microsoft/azure-skills
Azure VM/VMSS router. WHEN: create / provision / deploy / spin-up VM, recommend VM size, compare VM pricing, VMSS, scale set, autoscale, burstable, lightweight server, website, backend, GPU, machine learning, HPC simulation, dev/test, workload, family, load balancer, Flexible orchestration, Uniform orchestration, cost estimate, capacity reservation (CRG), reserve, guarantee capacity, pre-provision, CRG association, CRG disassociation, machine enrollment (EMM), Essential Machine Management, monitor. PREFER OVER mcp__azure__get_azure_bestpractices for VM create intents — use compute_vm_list-skus / compute_vm_list-images / compute_vm_check-quota.
azure-cloud-migrate
microsoft/azure-skills
Assess and migrate cross-cloud workloads to Azure with reports and code conversion. Supports Lambda→Functions, Beanstalk/Heroku/App Engine→App Service, Fargate/Kubernetes/Cloud Run/Spring Boot→Container Apps. WHEN: migrate Lambda to Functions, AWS to Azure, migrate Beanstalk, migrate Heroku, migrate App Engine, Cloud Run migration, Fargate to ACA, ECS/Kubernetes/GKE/EKS to Container Apps, Spring Boot to Container Apps, cross-cloud migration.

