Phase 6 · Testing, Delivery & ProductionModule 43~72 min read

TypeScript for Production Node.js

Migrate Node.js boundaries to strict TypeScript with narrowing, generics, runtime validation, typed errors, and correct ESM configuration.

What you'll learn

TypeScript can make domain states and dependency contracts explicit, but network, environment, and database values remain unknown at runtime. Combine strict compilation with runtime validation and a correct ESM build.

By the end of this lesson, you'll be able to:

  • Configure strict TypeScript and ESM
  • Model domain states and errors
  • Narrow unknown boundary input
  • Ship source maps and declarations where needed

Core mental model

Node.js becomes easier when you separate the JavaScript language from the runtime and the operating-system capabilities it exposes. Use this table as a decision guide.

ConceptWhat it meansDecision rule
unknownA value requiring proof before useUse at external boundaries instead of any
Discriminated unionVariants identified by a literal fieldModel finite states and require exhaustive handling
Runtime schemaExecutable validation of real valuesParse HTTP, config, queue, and persistence boundaries

Professional workflow

Build and verify Node.js programs from the terminal in small, observable steps.

  1. Define the typed Node.js boundary boundary: inputs, outputs, invariants, ownership, and expected failures.
  2. Design the data or message contract before choosing implementation details.
  3. Implement the smallest correct path with dependencies passed explicitly.
  4. Add validation, failure translation, cleanup, and concurrency behavior.
  5. Verify the boundary with realistic data and at least one adversarial case.
  6. Measure or observe the behavior before optimizing or extracting abstractions.

Keep the feedback loop short

Run the smallest useful command after every meaningful change. Read the complete error message before editing again, and keep inputs and outputs visible while you learn.

Guided code lab

Make invalid job states harder to express

A discriminant narrows the available fields and the never check makes new states a compile-time obligation.

job.ts
type Job =
  | { state: 'queued'; queuedAt: Date }
  | { state: 'running'; startedAt: Date; progress: number }
  | { state: 'failed'; failedAt: Date; reason: string }
  | { state: 'complete'; completedAt: Date; resultKey: string };

function label(job: Job): string {
  switch (job.state) {
    case 'queued': return 'Waiting';
    case 'running': return Math.round(job.progress * 100) + '%';
    case 'failed': return job.reason;
    case 'complete': return job.resultKey;
    default: { const neverJob: never = job; return neverJob; }
  }
}

Production practice

Contract

Static types describe trusted in-process values; runtime schemas earn trust for anything crossing a process or storage boundary.

Verification

Run typecheck separately, test schema acceptance/rejection, verify emitted ESM imports, stack traces, declaration output, and package exports.

Operations

Pin compiler settings, publish source maps securely, avoid transpile-only production gates, and track validation failures by boundary.

Common failure mode

A type assertion changes no runtime value. Casting request.body as TaskInput can silence the compiler while accepting attacker data.

Independent workshop

Migrate one API vertical slice to strict TypeScript.

Your finished workshop must include:

  • Strict tsconfig
  • Domain unions
  • Typed ports
  • Runtime request/config schemas
  • Typed errors
  • Build/typecheck tests

Definition of done

Run the happy path and at least two edge cases, keep responsibilities separated, and add a short README explaining how to run the program.

Recap & quick check

Key takeaways

  • Types disappear at runtime
  • Unknown requires narrowing
  • Unions model states
  • Assertions do not validate
  • Compiler and runtime checks cooperate

Quick check

1. What type fits untrusted JSON?

2. Does as TaskInput validate data?

3. Why use a discriminated union?

Next: Docker, Images & Local Infrastructure