Treat every value that entered outside the current TypeScript program as unknown until runtime evidence establishes its shape. TypeScript can prove relationships inside checked code, but its types are erased from emitted JavaScript. An annotation on response.json() is a promise made by the programmer, not a check performed on the network payload.
Mark the actual trust boundaries
HTTP bodies, query parameters, database JSON, cache entries, environment variables, postMessage events, file contents, queue messages, and third-party SDK results all cross a runtime boundary. So do values written by an older version of your own service. Assigning any of them directly to a domain type lets invalid data travel until a distant property access fails or, worse, produces a plausible wrong result.
The TypeScript handbook explains that types are erased during compilation; the resulting JavaScript carries no interface definitions that automatically inspect values. Type assertions are also compile-time constructs. Prefer unknown at adapters so code must narrow or validate before use, and reserve any for deliberately untyped escape hatches with a small scope.
Keep one schema authoritative
Avoid writing an interface, a validator, an OpenAPI schema, and documentation independently. Choose a source of truth that fits the boundary. In a schema-first API, generate TypeScript types and validators from OpenAPI or JSON Schema. In a code-first application, use a runtime schema library that infers the static type, then export a machine-readable schema if the tool supports it. Generated files should be reproducible and checked for drift.
JSON Schema’s 2020-12 core specification defines a vocabulary for describing and validating JSON instances. Validation proves only the declared constraints. A format annotation, business rule, or cross-record invariant may require additional code depending on the validator and schema. Keep syntactic parsing separate from semantic checks such as “end date follows start date” or “tenant owns project.”
Map valid transport data into a domain value instead of passing decoded objects throughout the application. A boundary adapter can normalize dates, trim identifiers only when the contract permits it, reject unknown enum members, and produce a branded type. This keeps compatibility decisions near the protocol. It also gives errors a stable path and reason without exposing raw payloads.
Validate on both reads and writes
Validating incoming writes protects new data; validating reads detects corruption, stale formats, and writers that bypassed the service. For a database row, validate at the repository adapter, then translate failures into an observable data-quality error. Do not silently substitute defaults for required security fields. For environment configuration, parse once during startup and stop with a useful error rather than discovering that PORT=abc under load.
For postMessage, validate both event.origin and event.data. A structurally valid message from an untrusted origin is still untrusted. For SDK responses, validate fields your decisions depend on even if the SDK ships declarations; network services and package versions can disagree.
Decide how strict unknown properties should be. Rejecting them catches misspellings and smuggling, but it can break forward compatibility when providers add fields. A good adapter can ignore unknown response fields while rejecting unknown command fields. Version stored or queued envelopes explicitly when their lifetime exceeds one deployment.
Test failures at the adapter
Use examples for missing fields, wrong scalar types, oversized arrays, invalid dates, unexpected enum values, and extra properties. Fuzz deeply nested or recursive inputs and set size limits before parsing. Assert that application services receive only validated domain values. Track validation failures by boundary and schema version without logging secrets.
The next step is to replace one unsafe as DomainType at an external boundary with unknown, a runtime schema, and an explicit mapper. Review by 2026-12-05 or when TypeScript, the schema standard, or a generator version changes.
Sources are linked throughout this guide. Product capabilities can change; consult the linked documentation for your deployment.
Read our editorial approach ↗