Use when creating, modernizing, or production-hardening a Node.js backend service after backend architecture is approved or intentionally deferred. Produces a framework-aware (Express, Fastify, or NestJS) TypeScript service shell with fail-fast validated configuration, a structured pino logging seam, liveness and readiness probes, layered error handling (uncaughtException, unhandledRejection, framework error handler, graceful shutdown), an AsyncLocalStorage request context, a DI container shell downstream archetypes extend, and a non-root multi-stage container image without baked secrets. Do not use for auth flow implementation, observability vendor wiring, queue or event integration, or performance and resilience gating; use the other Node.js archetype skills instead.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Use when creating, modernizing, or production-hardening a Node.js backend service after backend architecture is approved or intentionally deferred. Produces a framework-aware (Express, Fastify, or NestJS) TypeScript service shell with fail-fast validated configuration, a structured pino logging seam, liveness and readiness probes, layered error handling (uncaughtException, unhandledRejection, framework error handler, graceful shutdown), an AsyncLocalStorage request context, a DI container shell downstream archetypes extend, and a non-root multi-stage container image without baked secrets. Do not use for auth flow implementation, observability vendor wiring, queue or event integration, or performance and resilience gating; use the other Node.js archetype skills instead.
Node.js Service Scaffold
When to use
Invoke when starting a new Node.js backend service, standardizing an existing service baseline, modernizing a service that lacks validated config, structured logging, health probes, or layered error handling, or preparing a Node.js service for container deployment.
Do not use for: auth, session, or OWASP review work (use nodejs-auth-and-security-review), observability vendor wiring — OpenTelemetry, prom-client, SLO/alerts (use nodejs-observability-readiness), queue, broker, or event integration (use nodejs-queue-and-event-integration), or performance and resilience gating — backpressure, circuit breakers, load-test gates (use nodejs-performance-and-resilience).
Inputs
Required:
Service name and the business capability it serves.
Approved backend-architecture.md, or explicit confirmation that backend architecture is intentionally deferred.
Optional:
Approved architecture/security decisions on auth provider, session model, and secret handling.
Framework choice if backend-architecture.md is silent (Fastify default for new services; Express for ecosystem-constrained services; NestJS if a module/DI framework is mandated).
DI container preference if silent (the framework's built-in for NestJS; awilix default otherwise).
Data layer named by architecture (Prisma / Drizzle / TypeORM) — registered as a seam only, not implemented here.
Target Node.js LTS (20+ default), package manager, and monorepo vs single-service repo placement.
Operating rules
Never generate tutorial-grade scaffolding. Assume multiple environments, structured logging, container deployment, graceful shutdown, and operational ownership.
Consume backend-architecture.md; do not invent decisions. Framework, domain boundaries, data layer, and contracts belong to backend-architecture.md; auth provider and secret handling belong to architecture/security. If either is silent on a decision this scaffold needs, pause and raise an ADR candidate rather than guessing.
Owns the DI and principal-provider baseline only — the shell, not the flow. Token verification, session, and protected-route logic belong to nodejs-auth-and-security-review. Scaffold the seam; do not implement the flow.
Secure by default: configuration is validated at boot and fails fast; no secrets in source, committed config, the image, or .env; the container runs as a non-root user from a digest-pinned base image.
Configuration is validated, not read ad hoc: a single typed, frozen config object built from a schema (zod or equivalent) at startup. Missing or malformed required variables abort boot with a clear message — never a runtime undefined.
Error handling is layered in a specific order: process.on('uncaughtException') and process.on('unhandledRejection') (log fatal, flush, exit non-zero) → the framework error handler (Express error middleware / Fastify setErrorHandler / Nest exception filter; structured body, no stack or internals in non-dev) → graceful shutdown on SIGTERM/SIGINT (stop accepting, drain in-flight, close server and registered resources within a bounded timeout, then exit). All four are required.
Observability is a seam, not an implementation: a structured pino logger bound to an AsyncLocalStorage request context (request id correlated) is mandatory in the baseline; OpenTelemetry, metrics, and alerting are explicitly deferred to nodejs-observability-readiness behind a no-op tracer/metrics interface.
Health probes are mandatory: a liveness endpoint (process is up) and a readiness endpoint (registered dependency checks pass) are separate and wired before any feature route.
A scaffold that does not build is not done. Run typecheck, lint, the test run, and a boot smoke check before declaring completion; fix and re-run on failure.
Confirm the target directory before writing files. Recommend services/<service-name>/ in a monorepo or repo root for a single-service repo. Refuse to write into a plugin or skill repository without explicit user override.
Output contract
The generated service shell MUST conform to:
deployment-standards — env-agnostic image; runtime config injected, not baked; one artifact per environment; non-root container.
observability-standards — structured logging with request-id correlation and an environment tag; tracer/metrics seam present.
security-standards — no secrets in source, committed config, or image; config fails fast on missing required secrets.
Upstream contract: backend-architecture.md is the source of truth for framework, domain boundaries, data layer, and contracts; architecture/security is the source of truth for auth provider, session model, and secret handling. If either is silent on a decision this scaffold needs, pause and raise an ADR candidate rather than guessing.
Progressive references
Read references/nodejs-scaffold-playbook.md when implementing any owned area or checking the anti-pattern list.
Read references/nodejs-scaffold-quality-rubric.md before declaring the scaffold complete.
Use assets/nodejs-service-scaffold.template.md as the directory-layout, config, and entry-point reference.
Process
Gather context: service name, business capability, target directory, target Node LTS, package manager, and the auth provider decision from architecture/security. Load backend-architecture.md and extract framework, domain boundaries, data layer, and contracts. If a needed decision is missing, raise an ADR candidate before proceeding. Confirm the target directory is not a plugin or skill repository.
Generate the project layout: package.json (deps pinned exactly — no ^ or ~, engines.node set), tsconfig.json (strict: true), eslint/lint config, .gitignore, and the directory structure (feature/domain layout per backend-architecture.md). Reference assets/nodejs-service-scaffold.template.md for the canonical layout.
Generate validated configuration: a schema (zod or equivalent) in src/config/index.ts parsing process.env at boot, producing a single frozen typed config object; abort with a clear message on any missing or malformed required variable. Create .env.example documenting every variable with placeholder values only.
Generate the structured logging and request-context seam: a pino logger in src/observability/logger.ts; an AsyncLocalStorage request context in src/observability/context.ts; framework middleware/hook that assigns or propagates a request id and binds a child logger. Add a no-op tracer/metrics interface in src/observability/telemetry.ts with explicit TODO comments naming nodejs-observability-readiness as the owner.
Generate the layered error handling: process-level uncaughtException and unhandledRejection handlers (log fatal, flush logs, exit non-zero) in src/server/process-handlers.ts; the framework error handler (Express error middleware / Fastify setErrorHandler / Nest exception filter) returning a structured body with no stack or internals in non-dev; graceful shutdown on SIGTERM/SIGINT in src/server/shutdown.ts that stops accepting connections, drains in-flight requests, closes the server and registered resource hooks within a bounded timeout, then exits.
Generate health probes: a liveness endpoint (/healthz) returning 200 when the process is up, and a readiness endpoint () iterating registered dependency-check hooks and returning 503 until all pass. Wire both before any feature route.
Outputs
Required:
TypeScript service source tree with package.json (pinned deps, engines.node), tsconfig.json (strict), and lint config.
Validated, frozen typed configuration with fail-fast boot and .env.example.
Separate liveness (/healthz) and readiness (/readyz) probes.
DI container with a typed principal-provider shell (auth flow explicitly deferred).
Multi-stage non-root Dockerfile from a digest-pinned base, .dockerignore, documented HEALTHCHECK.
Service README listing all seams downstream archetypes fill.
Output rules:
Generated files are functional and the service boots, not placeholder-heavy.
No secrets in source, committed config, .env.example, or the image.
The principal provider is a seam, not a flow — token verification, session, and route guards are explicitly deferred to nodejs-auth-and-security-review.
The same image runs in every environment; configuration arrives at runtime via environment variables.
Quality checks
tsc --noEmit reports zero errors and the lint command passes.
The test command runs and the boot smoke check (GET /healthz → 200, clean shutdown) passes, or a skip is documented with reason.
package.json pins every dependency (no ^/~) and sets engines.node.
Configuration is schema-validated at boot and aborts on a missing required variable — verified by removing one required var and observing a clear non-zero exit.
No secrets appear in source, committed config, .env.example, or the built image.
All four error layers are wired: uncaughtException, unhandledRejection, framework error handler, graceful shutdown on SIGTERM/SIGINT.
The framework error handler returns no stack trace or internal details in non-dev environments.
/healthz and /readyz are separate; /readyz returns 503 until registered dependency checks pass.
The pino logger binds a request id from AsyncLocalStorage; the tracer/metrics seam is a no-op with a TODO naming nodejs-observability-readiness.
The DI principal provider is a typed shell tied to an architecture/security decision; token/session logic is explicitly deferred with TODO comments.
The Dockerfile is multi-stage, runs as non-root, uses a digest-pinned base, and bakes no secrets.
The service README lists all seams downstream archetypes are expected to fill.
Generate the DI and principal-provider baseline: the container from backend-architecture.md (Nest built-in, or awilix/tsyringe) in src/container/index.ts. Register the principal/auth-context provider as a typed shell. Add explicit TODO comments naming nodejs-auth-and-security-review as the owner of token verification, session, and protected-route logic, and the data-layer client as a seam owned by the data implementation.
Generate container packaging: a multi-stage Dockerfile (build stage, slim runtime stage) running as a non-root user from a digest-pinned base image, a .dockerignore, and a documented HEALTHCHECK hitting /healthz. No secrets baked; runtime config arrives via environment.
Generate local-run documentation in the service README: how to run each environment, every required environment variable (mirrored from .env.example), the runtime-config contract, and the explicit table of seams downstream archetypes fill (auth, observability vendor, queue/event, performance gates, data layer).
Build verification (mandatory): run tsc --noEmit, the lint command, the test command (vitest/jest), and a boot smoke check (start the server, assert GET /healthz returns 200, shut down cleanly). Fix and re-run on failure. Then validate against the standards in the Output contract; revise until all pass or explicitly document any unresolved gap in the README.