← Back to the library
FoundationsCore · 6 min

Types pass, but production data breaks the app

“tsc is green and tests pass, yet production throws "Cannot read properties of undefined" on a field the type says always exists, or a createdAt typed as Date turns out to be a string.”

THE PRINCIPLE

Types describe data you have already checked. Everything crossing a boundary (network, disk, env, user input, other teams' services) starts as unknown.

FIRST MOVES

  1. Grep for as casts, JSON.parse results, res.json() annotations, and non-null ! on external data; those are the unchecked claims.
  2. List the boundaries: HTTP request bodies, API responses, webhooks, queue messages, env vars, localStorage, raw SQL rows.
  3. Parse each one with a runtime schema at the entry point and pass only the parsed value inward; treat failures as a contract error with logging, not a crash deep in the UI.
  4. Remember JSON has no Date, BigInt, or undefined: a field typed Date arrives as a string unless the schema converts it.
  5. Tighten the compiler where it helps: strict, noUncheckedIndexedAccess, and unknown instead of any for catch variables and parsed input.

TOOLS: THEN → NOW

Hand-written interfaces and as casts for API dataRuntime schemas (Zod, Valibot, ArkType) that infer the static type
Hand-maintained client typesTypes generated from the OpenAPI or GraphQL schema, still validated where you don't control the producer

PATTERN SNAPSHOT

import * as z from "zod";

// Compiles, but nothing checked it: a missing address crashes later.
const unchecked = (await res.json()) as { id: string; address: { city: string } };

const User = z.object({
  id: z.string(),
  address: z.object({ city: z.string() }).nullable(),
  createdAt: z.iso.datetime().transform((s) => new Date(s)),
});
type User = z.infer<typeof User>;

const parsed = User.safeParse(await res.json());
if (!parsed.success) throw new Error("Upstream user contract changed", { cause: parsed.error });
const user: User = parsed.data; // user.address?.city, user.createdAt is a real Date

CLOSE THE AI. EXPLAIN THIS.

What runtime code does `as User` produce, and where in a request's life should the check that makes the type true happen?

SOURCES

Guide reviewed